cropper下载 https://download.csdn.net/download/dongyan3595/90970115
前端代码
<!doctype html>
<html lang="en">
<head>
<base href="/aishop/">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="assets/fonts/font-awesome/css/font-awesome.css"/>
<link rel="stylesheet" type="text/css" href="assets/js/vendor/bootstrap/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="assets/js/vendor/cropper/cropper.min.css"/>
<style>
.upload-image {height: 100%; opacity: 0;}
.upload-image-preview {width: 200px;height: 200px;overflow: hidden;margin: 0 44px;display: inline-block;}
#imageBox {height: 451px;overflow: auto;background-color: #F1F1F1;}
#imagePreviewBox .upload-image-preview {background-color: #F1F1F1;}
.cropper-container {
height:432px!important;
width: 529px!important;
}
.cropper-crop-box{
max-height:430px!important;
max-width:527px!important;
}
.mosaic-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: auto; /* 改为auto,允许鼠标事件 */
z-index: 1000;
}
.mosaic-active {
cursor: crosshair !important;
}
</style>
</head>
<div>
<div id="content" class="edit-content" style="padding: 5px; opacity: 0;">
<div id="imageBox" class="col-xs-8" style="position: relative;">
<img id="image" class="upload-image" src="assets/images/ruanzhu.jpg">
<canvas id="mosaicCanvas" class="mosaic-canvas" style="display:none;"></canvas>
</div>
<div id="imagePreviewBox" class="col-xs-4">
<div class="upload-image-preview"></div>
</div>
<div class="col-xs-4" style="text-align: center;">
<button id="dragBtn" type="button" class="btn btn-danger fa fa-arrows" onclick="dragImage()" title="移动"></button>
<button type="button" class="btn btn-danger fa fa-search-plus" onclick="zoomPlus()" title="放大图片"></button>
<button type="button" class="btn btn-danger fa fa-search-minus" onclick="zoomMinus()" title="缩小图片"></button>
<button type="button" class="btn btn-danger fa fa-refresh" onclick="reset()" title="重置图片"></button>
<button id="mosaicBtn" type="button" class="btn btn-warning fa fa-th" onclick="toggleMosaic()" title="马赛克"></button>
<button type="button" class="btn btn-info fa fa-reply" onclick="undoEdit()" title="撤销" id="undoBtn" disabled></button>
</div>
<div class="col-xs-4" style="margin-top: 5px; text-align: center;">
<div class="btn-group">
<button class="btn btn-danger fa fa-undo" onclick="rateLeft()" type="button" title="向左旋转90°"> 向左旋转
</button>
</div>
<div class="btn-group">
<button class="btn btn-danger fa fa-repeat" onclick="rateRight()" type="button" title="向右旋转90°"> 向右旋转
</button>
</div>
</div>
<div class="col-xs-4" style="margin-top: 5px; text-align: center;">
<div class="btn-group">
<form id="uploadFileForm" action="#" style="display: none;">
<input type="file" id="uploadImage" name="image" accept="image/jpeg,image/gif,image/png,image/jpg" onchange="doUploadFile()"/>
</form>
<button class="btn btn-primary btn-block fa fa-upload" type="button" onclick="openUploadImage()"> 上传图片
</button>
</div>
<div class="btn-group">
<button class="btn btn-primary btn-block fa fa-save" type="button" onclick="doCrop()"> 裁剪上传</button>
</div>
</div>
<div class="col-xs-12" style="margin-top: 5px; text-align: center;">
<div class="form-group">
<label for="mosaicSize">马赛克大小:</label>
<input type="range" id="mosaicSize" min="5" max="30" value="10" class="form-control">
<span id="mosaicSizeValue">10px</span>
</div>
</div>
<div class="col-xs-12" style="margin-top: 5px; text-align: center;">
<div class="form-group">
<label for="imageSize">图片压缩:</label>
<input type="range" id="imageSize" min="10" max="100" value="100" class="form-control">
<span id="imageSizeValue">100%</span>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="assets/js/jquery-3.5.1.min.js"></script>
<script type="text/javascript" src="assets/js/vendor/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="assets/js/vendor/cropper/cropper.min.js"></script>
<script type="text/javascript" src="assets/js/common.js"></script>
<script src="assets/layuiadmin/layui/layui.js"></script>
<script type="text/javascript">
var toggleMosaic = function() {}
var undoEdit = function() {}
var doCrop = function() {}
var zoomPlus= function() {}
var zoomMinus= function() {}
var reset= function() {}
var rateLeft= function() {}
var rateRight= function() {}
var dragImage= function() {}
var openUploadImage= function() {}
var doUploadFile= function() {}
layui.config({
base: 'assets/layuiadmin/'
}).extend({
index: 'lib/index'
}).use(['index', 'restajax', 'datamessage', 'dialog'], function() {
var restAjax = layui.restajax;
var dialog = layui.dialog;
var dataMessage = layui.datamessage;
function doSubmitForm() {
return false;
}
var cropper;
var isMosaicMode = false;
var mosaicCanvas = document.getElementById('mosaicCanvas');
var mosaicCtx = mosaicCanvas.getContext('2d');
var isDrawing = false;
var lastX, lastY;
var editHistory = [];
var currentHistoryIndex = -1;
var maxHistorySteps = 10;
// 放大
zoomPlus = function () {
cropper.zoom(0.1);
}
// 缩小
zoomMinus = function () {
cropper.zoom(-0.1);
}
// 逆时针90
rateLeft = function () {
cropper.rotate(-90);
}
// 顺时针90
rateRight = function () {
cropper.rotate(90);
}
// 重置
reset = function () {
cropper.reset();
}
// 图片移动
var isDrag = false;
dragImage = function () {
if (!isDrag) {
cropper.setDragMode('move');
isDrag = true;
$('#dragBtn').addClass('btn-default');
$('#dragBtn').removeClass('btn-danger');
} else {
cropper.setDragMode('crop');
isDrag = false;
$('#dragBtn').addClass('btn-danger');
$('#dragBtn').removeClass('btn-default');
}
}
// 切换马赛克模式
toggleMosaic = function() {
isMosaicMode = !isMosaicMode;
if (isMosaicMode) {
// 进入马赛克模式
$('#mosaicBtn').addClass('btn-success');
$('#mosaicBtn').removeClass('btn-warning');
// 保存当前状态到历史记录
saveToHistory();
// 显示马赛克画布
setupMosaicCanvas();
$('#mosaicCanvas').show();
// 重要:将画布设置为可接收鼠标事件
mosaicCanvas.style.pointerEvents = 'auto';
// 添加鼠标事件
$(mosaicCanvas).on('mousedown', startMosaic);
$(document).on('mousemove', drawMosaic);
$(document).on('mouseup mouseleave', stopMosaic);
// 添加触摸事件
$(mosaicCanvas).on('touchstart', handleTouchStart);
$(document).on('touchmove', handleTouchMove);
$(document).on('touchend touchcancel', handleTouchEnd);
// 添加马赛克模式样式
$('#imageBox').addClass('mosaic-active');
} else {
// 退出马赛克模式
$('#mosaicBtn').addClass('btn-warning');
$('#mosaicBtn').removeClass('btn-success');
// 重要:将画布设置为不可接收鼠标事件
mosaicCanvas.style.pointerEvents = 'none';
// 移除事件监听
$(mosaicCanvas).off('mousedown');
$(document).off('mousemove', drawMosaic);
$(document).off('mouseup mouseleave', stopMosaic);
$(mosaicCanvas).off('touchstart');
$(document).off('touchmove', handleTouchMove);
$(document).off('touchend touchcancel', handleTouchEnd);
// 移除马赛克模式样式
$('#imageBox').removeClass('mosaic-active');
}
}
// 设置马赛克画布
function setupMosaicCanvas() {
var cropperCanvas = $('.cropper-canvas')[0];
var cropperImage = $('.cropper-canvas img')[0];
if (cropperCanvas && cropperImage) {
var containerRect = cropperCanvas.getBoundingClientRect();
var imageBoxRect = $('#imageBox')[0].getBoundingClientRect();
// 设置画布尺寸
mosaicCanvas.width = containerRect.width;
mosaicCanvas.height = containerRect.height;
mosaicCanvas.style.width = containerRect.width + 'px';
mosaicCanvas.style.height = containerRect.height + 'px';
// 设置画布位置 - 相对于imageBox计算偏移
mosaicCanvas.style.position = 'absolute';
mosaicCanvas.style.top = (containerRect.top - imageBoxRect.top) + 'px';
mosaicCanvas.style.left = (containerRect.left - imageBoxRect.left) + 'px';
// 清除画布
mosaicCtx.clearRect(0, 0, mosaicCanvas.width, mosaicCanvas.height);
}
}
// 开始绘制马赛克
function startMosaic(e) {
e.preventDefault(); // 阻止默认行为
isDrawing = true;
var rect = mosaicCanvas.getBoundingClientRect();
lastX = e.clientX - rect.left;
lastY = e.clientY - rect.top;
// 立即开始绘制一个点
drawMosaicEffect(lastX, lastY, lastX, lastY);
}
// 处理触摸开始事件
function handleTouchStart(e) {
e.preventDefault();
var touch = e.originalEvent.touches[0];
var rect = mosaicCanvas.getBoundingClientRect();
lastX = touch.clientX - rect.left;
lastY = touch.clientY - rect.top;
isDrawing = true;
}
// 处理触摸移动事件
function handleTouchMove(e) {
if (!isDrawing || !isMosaicMode) return;
e.preventDefault();
var touch = e.originalEvent.touches[0];
var rect = mosaicCanvas.getBoundingClientRect();
var x = touch.clientX - rect.left;
var y = touch.clientY - rect.top;
drawMosaicEffect(lastX, lastY, x, y);
lastX = x;
lastY = y;
}
// 处理触摸结束事件
function handleTouchEnd(e) {
isDrawing = false;
}
// 绘制马赛克
function drawMosaic(e) {
if (!isDrawing || !isMosaicMode) return;
var rect = mosaicCanvas.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
drawMosaicEffect(lastX, lastY, x, y);
lastX = x;
lastY = y;
}
// 绘制马赛克效果
function drawMosaicEffect(startX, startY, endX, endY) {
var cropperImage = $('.cropper-canvas img')[0];
if (!cropperImage) return;
var mosaicSize = parseInt($('#mosaicSize').val());
var dx = endX - startX;
var dy = endY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var steps = Math.max(Math.floor(distance), 1);
for (var i = 0; i <= steps; i++) {
var x = startX + (dx * i / steps);
var y = startY + (dy * i / steps);
// 计算马赛克块的左上角坐标
var mosaicX = Math.floor(x / mosaicSize) * mosaicSize;
var mosaicY = Math.floor(y / mosaicSize) * mosaicSize;
// 从原始图像获取像素数据
var cropperRect = cropperImage.getBoundingClientRect();
var cropperCanvas = document.createElement('canvas');
cropperCanvas.width = mosaicSize;
cropperCanvas.height = mosaicSize;
var cropperCtx = cropperCanvas.getContext('2d');
try {
// 获取马赛克区域的图像数据
cropperCtx.drawImage(
cropperImage,
mosaicX, mosaicY, mosaicSize, mosaicSize,
0, 0, mosaicSize, mosaicSize
);
// 计算平均颜色
var imageData = cropperCtx.getImageData(0, 0, mosaicSize, mosaicSize);
var data = imageData.data;
var r = 0, g = 0, b = 0, a = 0, count = 0;
for (var j = 0; j < data.length; j += 4) {
r += data[j];
g += data[j + 1];
b += data[j + 2];
a += data[j + 3];
count++;
}
// 计算平均值
r = Math.round(r / count);
g = Math.round(g / count);
b = Math.round(b / count);
a = Math.round(a / count);
// 绘制马赛克块
mosaicCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a / 255})`;
mosaicCtx.fillRect(mosaicX, mosaicY, mosaicSize, mosaicSize);
} catch (error) {
console.error('马赛克绘制错误:', error);
}
}
}
// 停止绘制马赛克
function stopMosaic() {
if (isDrawing) {
isDrawing = false;
saveToHistory();
}
}
// 保存到历史记录
function saveToHistory() {
// 如果当前不是最新状态,删除后面的历史
if (currentHistoryIndex >= 0 && currentHistoryIndex < editHistory.length - 1) {
editHistory = editHistory.slice(0, currentHistoryIndex + 1);
}
// 获取当前画布状态
var imageData = null;
if (mosaicCanvas.width > 0 && mosaicCanvas.height > 0) {
try {
imageData = mosaicCtx.getImageData(0, 0, mosaicCanvas.width, mosaicCanvas.height);
} catch (e) {
console.error('获取画布数据失败:', e);
}
}
// 添加到历史记录
if (imageData) {
editHistory.push(imageData);
// 限制历史记录数量
if (editHistory.length > maxHistorySteps) {
editHistory.shift();
}
currentHistoryIndex = editHistory.length - 1;
// 启用撤销按钮
$('#undoBtn').prop('disabled', false);
}
}
// 撤销编辑
undoEdit = function () {
if (currentHistoryIndex > 0) {
currentHistoryIndex--;
var imageData = editHistory[currentHistoryIndex];
// 恢复到之前的状态
mosaicCtx.putImageData(imageData, 0, 0);
} else if (currentHistoryIndex === 0) {
// 恢复到初始状态(清空画布)
mosaicCtx.clearRect(0, 0, mosaicCanvas.width, mosaicCanvas.height);
currentHistoryIndex = -1;
$('#undoBtn').prop('disabled', true);
}
}
// 重置马赛克画布和历史记录
function resetMosaicCanvas() {
if (mosaicCanvas) {
mosaicCtx.clearRect(0, 0, mosaicCanvas.width, mosaicCanvas.height);
}
editHistory = [];
currentHistoryIndex = -1;
$('#undoBtn').prop('disabled', true);
}
// 打开上传图片
openUploadImage = function () {
$('#uploadImage').click();
}
// 添加键盘快捷键支持
$(document).on('keydown', function(e) {
// 检测 CTRL + Z
if (e.ctrlKey && e.keyCode === 90) {
// 阻止浏览器默认的撤销行为
e.preventDefault();
// 调用撤销函数
undoEdit();
}
});
// 上传文件
doUploadFile = function () {
var loadLayerIndex;
var formData = new FormData($('#uploadFileForm')[0]);
restAjax.postFile('api/goods/upload-image', formData, {}, function (code, data) {
dialog.msg('上传成功');
cropper.replace('route/file/download/false/' + data.data, false);
localStorage.setItem('uploadImage', data.data);
// 重置马赛克画布和历史记录
resetMosaicCanvas();
}, function(code, data) {
dialog.msg(data.msg);
}, function() {
loadLayerIndex = dialog.msg(dataMessage.uploading, {icon: 16, time: 0, shade: 0.3});
}, function() {
dialog.close(loadLayerIndex);
});
}
// 裁剪
doCrop = function () {
// 获取裁剪后的画布
var croppedCanvas = cropper.getCroppedCanvas();
// 如果有马赛克效果,将马赛克合并到裁剪后的图像上
if (mosaicCanvas.width > 0 && mosaicCanvas.height > 0) {
var cropperCanvas = $('.cropper-canvas')[0];
var cropBox = $('.cropper-crop-box')[0];
if (cropperCanvas && cropBox) {
var cropBoxRect = cropBox.getBoundingClientRect();
var cropperRect = cropperCanvas.getBoundingClientRect();
// 计算裁剪框相对于画布的位置
var left = cropBoxRect.left - cropperRect.left;
var top = cropBoxRect.top - cropperRect.top;
// 获取裁剪框的尺寸
var width = cropBoxRect.width;
var height = cropBoxRect.height;
// 在裁剪后的画布上绘制马赛克
var ctx = croppedCanvas.getContext('2d');
ctx.drawImage(
mosaicCanvas,
left, top, width, height,
0, 0, croppedCanvas.width, croppedCanvas.height
);
}
}
croppedCanvas.toBlob(function (cropBlob) {
var formData = new FormData();
formData.append("file", cropBlob);
formData.append("picturesThumbnails", $('#imageSize').val());
var loadLayerIndex;
restAjax.postFile('api/goods/upload-image', formData, {}, function (code, data) {
dialog.msg('裁剪成功');
// 完全重置马赛克画布和历史记录
resetMosaicCanvas();
// 替换图像
cropper.replace('route/file/download/false/' + data.data, false);
localStorage.setItem('uploadImage', data.data);
// 在图像加载完成后重新设置马赛克画布
setTimeout(function() {
setupMosaicCanvas();
}, 500);
}, function(code, data) {
dialog.msg(data.msg);
}, function() {
loadLayerIndex = dialog.msg(dataMessage.uploading, {icon: 16, time: 0, shade: 0.3});
}, function() {
dialog.close(loadLayerIndex);
});
});
}
$(function () {
var image = document.getElementById('image');
cropper = new Cropper(image, {
aspectRatio: 1.5 / 2,
viewMode: 1,
minContainerHeight: 430,
maxContainerHeight: 430,
preview: '.upload-image-preview',
crop(event) {
// 如果在马赛克模式下,实时更新马赛克画布位置
if (isMosaicMode) {
setupMosaicCanvas();
}
},
ready() {
// 初始化马赛克画布
setTimeout(function() {
setupMosaicCanvas();
}, 500);
}
});
$('#content').fadeTo(1000, 1);
var fileId = restAjax.params(window.location.href).fileId;
if (fileId != 'undefined' && fileId != undefined && fileId.length > 1) {
localStorage.setItem('uploadImage', fileId);
cropper.replace('route/file/download/false/' + fileId, false);
}
// 马赛克大小滑块事件
$('#mosaicSize').on('input', function() {
var size = $(this).val();
$('#mosaicSizeValue').text(size + 'px');
});
$('#imageSize').on('input', function() {
var size = $(this).val();
$('#imageSizeValue').text(size + '%');
});
// 确保马赛克画布初始化
setTimeout(function() {
setupMosaicCanvas();
}, 1000); // 延迟1秒,确保cropper已完全初始化
})
});
</script>
</body>
</html>
后台代码
@PostMapping({"upload-image"})
public SuccessResultData<String> uploadFile(@RequestParam("file") MultipartFile file, Double picturesThumbnails) throws IOException {
// 参数校验
if (file.isEmpty()) {
throw new SaveException("上传文件不能为空");
}
InputStream inputStream = file.getInputStream();
BufferedImage bufferedImage = ImageIO.read(inputStream);
if (bufferedImage == null) {
throw new SaveException("无效的图片文件");
}
BufferedImage image = bufferedImage;
if (picturesThumbnails != null) {
try {
image = Thumbnails.of(new BufferedImage[]{bufferedImage}).scale(1).outputQuality(picturesThumbnails / 100).outputFormat("jpg").asBufferedImage();
} catch (IOException e) {
throw new SaveException("图片压缩出现异常");
}
}
MultipartFile file1 = new MockMultipartFile(
"file", // 参数名(表单中的name)
UUIDUtil.get32UUID() + ".jpg", // 原始文件名
"image/jpeg",
convertToInputStream(image, "jpg") // 文件输入流
);
Map<String, Object> params1 = new HashMap<>();
String fileId1 = iFileService.uploadSingleByUserId(securityComponent.getCurrentUser().getUserId(), file1, UploadTypeEnum.IMAGE, params1);
return new SuccessResultData(fileId1);
}