如何在OpenSeadragon中以最高分辨率裁剪图片?
客户端用OpenSeadragon裁剪高分辨率图像并导出JPG
先解决你遇到的报错问题
你之前调用setCroppingPolygons时传的是rectForCrop,但这个方法要求传入多边形数组,也就是你定义的polygons变量,正确调用应该是:
polygons = [[rectForCrop.getTopLeft(), rectForCrop.getTopRight(), rectForCrop.getBottomRight(), rectForCrop.getBottomLeft()]] tiledImage.setCroppingPolygons(polygons);
但要注意:这个方法只是用来修改OpenSeadragon viewer里显示的图像裁剪区域,不能直接导出裁剪后的图片,所以你要实现导出需求的话,得用下面的方案。
客户端裁剪导出高分辨率JPG的实现方案
核心思路是:获取裁剪区域的原始图像坐标,加载对应区域的最高分辨率瓦片,拼接绘制到canvas后导出为JPG。
步骤1:转换坐标(如果用的是视口Rect)
如果你的rectForCrop是视口坐标(viewer内的坐标),先转成图像坐标(图像自身的0-1范围坐标):
const viewer = // 你的OpenSeadragon实例 const viewportRect = rectForCrop; // 用户提供的视口Rect const imageRect = viewer.viewport.viewportToImageRectangle(viewportRect);
步骤2:获取图像基础信息
const tiledImage = viewer.world.getItemAt(0); // 假设只有一张图,多图的话按需获取 const imageWidth = tiledImage.source.dimensions.x; // 图像原始宽度 const imageHeight = tiledImage.source.dimensions.y; // 图像原始高度 const maxLevel = tiledImage.source.levels.length - 1; // 最高分辨率级别 const level = tiledImage.source.levels[maxLevel]; const tileSize = tiledImage.source.tileSize; // 瓦片尺寸
步骤3:计算裁剪区域在最高分辨率图中的范围
// 图像坐标转原始像素坐标 const x = imageRect.x * imageWidth; const y = imageRect.y * imageHeight; const width = imageRect.width * imageWidth; const height = imageRect.height * imageHeight; // 最高分辨率图的尺寸 const levelWidth = level.width; const levelHeight = level.height; const scale = levelWidth / imageWidth; // 分辨率缩放系数 // 裁剪区域在最高分辨率图中的位置和尺寸 const levelX = x * scale; const levelY = y * scale; const levelCropWidth = width * scale; const levelCropHeight = height * scale;
步骤4:拼接瓦片到Canvas并导出
// 创建Canvas const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = Math.round(width); canvas.height = Math.round(height); // 计算需要加载的瓦片范围 const startTileX = Math.floor(levelX / tileSize); const endTileX = Math.floor((levelX + levelCropWidth) / tileSize); const startTileY = Math.floor(levelY / tileSize); const endTileY = Math.floor((levelY + levelCropHeight) / tileSize); const totalTiles = (endTileX - startTileX + 1) * (endTileY - startTileY + 1); let loadedTiles = 0; // 遍历加载每个瓦片并绘制 for (let tileX = startTileX; tileX <= endTileX; tileX++) { for (let tileY = startTileY; tileY <= endTileY; tileY++) { const tileUrl = tiledImage.source.getTileUrl(maxLevel, tileX, tileY); const img = new Image(); img.crossOrigin = 'anonymous'; // 跨域图片需要设置,服务器要允许跨域 img.onload = () => { // 计算瓦片在Canvas中的绘制位置 const tileCanvasX = (tileX * tileSize - levelX) / scale; const tileCanvasY = (tileY * tileSize - levelY) / scale; // 计算瓦片需要绘制的局部区域(边缘瓦片可能只取部分) const sourceX = Math.max(0, levelX - tileX * tileSize); const sourceY = Math.max(0, levelY - tileY * tileSize); const sourceW = Math.min(tileSize, levelX + levelCropWidth - tileX * tileSize); const sourceH = Math.min(tileSize, levelY + levelCropHeight - tileY * tileSize); // 绘制到Canvas ctx.drawImage( img, sourceX, sourceY, sourceW, sourceH, tileCanvasX, tileCanvasY, sourceW / scale, sourceH / scale ); loadedTiles++; if (loadedTiles === totalTiles) { // 所有瓦片绘制完成,导出JPG const jpgDataUrl = canvas.toDataURL('image/jpeg', 0.9); // 0.9为图片质量,范围0-1 // 后续操作:显示到页面或上传服务器 // 示例:显示预览图 const preview = document.createElement('img'); preview.src = jpgDataUrl; document.body.appendChild(preview); // 示例:上传到服务器(需转换为Blob) const blob = dataUrlToBlob(jpgDataUrl); const formData = new FormData(); formData.append('croppedImage', blob, 'crop.jpg'); fetch('/your-upload-endpoint', { method: 'POST', body: formData }); } }; img.src = tileUrl; } } // 辅助函数:DataURL转Blob function dataUrlToBlob(dataUrl) { const arr = dataUrl.split(','); const mime = arr[0].match(/:(.*?);/)[1]; const bstr = atob(arr[1]); const u8arr = new Uint8Array(bstr.length); for (let i = 0; i < bstr.length; i++) { u8arr[i] = bstr.charCodeAt(i); } return new Blob([u8arr], { type: mime }); }
注意事项
- 跨域图片需要服务器配置CORS,否则无法加载瓦片绘制
- 如果图像是单张大图(非瓦片),可以直接用
new Image()加载原图后裁剪,不需要处理瓦片 - 导出的JPG质量可以通过
toDataURL的第二个参数调整(0-1,1为最高质量)
内容的提问来源于stack exchange,提问作者gregster
相关产品推荐
相关产品推荐

