/*! * @antv/g-plugin-image-loader * @description A G plugin for loading image * @version 2.1.26 * @date 7/30/2025, 1:35:52 PM * @author AntVis * @docs https://g.antv.antgroup.com/ */ 'use strict'; var _classCallCheck = require('@babel/runtime/helpers/classCallCheck'); var _createClass = require('@babel/runtime/helpers/createClass'); var _callSuper = require('@babel/runtime/helpers/callSuper'); var _inherits = require('@babel/runtime/helpers/inherits'); var gLite = require('@antv/g-lite'); var _regeneratorRuntime = require('@babel/runtime/helpers/regeneratorRuntime'); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator'); var util = require('@antv/util'); var glMatrix = require('gl-matrix'); var RefCountCache = /*#__PURE__*/function () { function RefCountCache() { _classCallCheck(this, RefCountCache); this.cacheStore = new Map(); } return _createClass(RefCountCache, [{ key: "onRefAdded", value: function onRefAdded(ref) {} }, { key: "has", value: function has(key) { return this.cacheStore.has(key); } }, { key: "put", value: function put(key, item, ref) { if (this.cacheStore.has(key)) { return false; } this.cacheStore.set(key, { value: item, counter: new Set([ref.entity]) }); this.onRefAdded(ref); return true; } }, { key: "get", value: function get(key, ref) { var cacheItem = this.cacheStore.get(key); if (!cacheItem) { return null; } if (!cacheItem.counter.has(ref.entity)) { cacheItem.counter.add(ref.entity); this.onRefAdded(ref); } return cacheItem.value; } }, { key: "update", value: function update(key, value, ref) { var cacheItem = this.cacheStore.get(key); if (!cacheItem) { return false; } cacheItem.value = _objectSpread(_objectSpread({}, cacheItem.value), value); if (!cacheItem.counter.has(ref.entity)) { cacheItem.counter.add(ref.entity); this.onRefAdded(ref); } return true; } }, { key: "release", value: function release(key, ref) { var cacheItem = this.cacheStore.get(key); if (!cacheItem) { return false; } cacheItem.counter["delete"](ref.entity); if (cacheItem.counter.size <= 0) { this.cacheStore["delete"](key); } return true; } }, { key: "releaseRef", value: function releaseRef(ref) { var _this = this; Array.from(this.cacheStore.keys()).forEach(function (key) { _this.release(key, ref); }); } }, { key: "getSize", value: function getSize() { return this.cacheStore.size; } }, { key: "clear", value: function clear() { this.cacheStore.clear(); } }]); }(); var tasks = []; var nextFrameTasks = []; var ImageSlicer = /*#__PURE__*/function () { function ImageSlicer() { _classCallCheck(this, ImageSlicer); } return _createClass(ImageSlicer, null, [{ key: "stop", value: function stop() { var api = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ImageSlicer.api; if (ImageSlicer.rafId) { api.cancelAnimationFrame(ImageSlicer.rafId); ImageSlicer.rafId = null; } } }, { key: "executeTask", value: function executeTask() { var api = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ImageSlicer.api; if (tasks.length <= 0 && nextFrameTasks.length <= 0) { return; } nextFrameTasks.forEach(function (task) { return task(); }); nextFrameTasks = tasks.splice(0, ImageSlicer.TASK_NUM_PER_FRAME); ImageSlicer.rafId = api.requestAnimationFrame(function () { ImageSlicer.executeTask(api); }); } }, { key: "sliceImage", value: function sliceImage(image, sliceWidth, sliceHeight, rerender) { var overlap = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; var api = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : ImageSlicer.api; var imageWidth = image.naturalWidth || image.width; var imageHeight = image.naturalHeight || image.height; // 计算步长(考虑重叠区域) var strideW = sliceWidth - overlap; var strideH = sliceHeight - overlap; // 计算网格尺寸 var gridCols = Math.ceil(imageWidth / strideW); var gridRows = Math.ceil(imageHeight / strideH); var result = { tileSize: [sliceWidth, sliceHeight], gridSize: [gridRows, gridCols], tiles: Array(gridRows).fill(null).map(function () { return Array(gridCols).fill(null); }) }; // 遍历网格创建切片 var _loop = function _loop(row) { var _loop2 = function _loop2(col) { tasks.push(function () { // 计算当前切片的坐标 var startX = col * strideW; var startY = row * strideH; // 处理最后一列/行的特殊情况 var _ref = [Math.min(sliceWidth, imageWidth - startX), Math.min(sliceHeight, imageHeight - startY)], tempSliceWidth = _ref[0], tempSliceHeight = _ref[1]; // 创建切片canvas var sliceCanvas = api.createCanvas(); sliceCanvas.width = sliceWidth; sliceCanvas.height = sliceHeight; var sliceCtx = sliceCanvas.getContext('2d'); // 将图像部分绘制到切片canvas上 sliceCtx.drawImage(image, startX, startY, tempSliceWidth, tempSliceHeight, 0, 0, tempSliceWidth, tempSliceHeight); // 存储切片信息 result.tiles[row][col] = { x: startX, y: startY, tileX: col, tileY: row, data: sliceCanvas }; rerender(); }); }; for (var col = 0; col < gridCols; col++) { _loop2(col); } }; for (var row = 0; row < gridRows; row++) { _loop(row); } ImageSlicer.stop(); ImageSlicer.executeTask(); return result; } }]); }(); ImageSlicer.TASK_NUM_PER_FRAME = 10; var IMAGE_CACHE = new RefCountCache(); IMAGE_CACHE.onRefAdded = function onRefAdded(ref) { var _this = this; ref.addEventListener(gLite.ElementEvent.DESTROY, function () { _this.releaseRef(ref); }, { once: true }); }; var ImagePool = /*#__PURE__*/function () { function ImagePool(context, runtime) { _classCallCheck(this, ImagePool); this.gradientCache = {}; this.patternCache = {}; this.context = context; this.runtime = runtime; } return _createClass(ImagePool, [{ key: "getImageSync", value: function getImageSync(src, ref, callback) { var imageSource = util.isString(src) ? src : src.src; if (IMAGE_CACHE.has(imageSource)) { var imageCache = IMAGE_CACHE.get(imageSource, ref); if (imageCache.img.complete) { callback === null || callback === void 0 || callback(imageCache); return imageCache; } } this.getOrCreateImage(src, ref).then(function (cache) { callback === null || callback === void 0 || callback(cache); })["catch"](function (reason) { console.error(reason); }); return null; } }, { key: "getOrCreateImage", value: function getOrCreateImage(src, ref) { var _this2 = this; var imageSource = util.isString(src) ? src : src.src; if (!util.isString(src) && !IMAGE_CACHE.has(imageSource)) { var imageCache = { img: src, size: [src.naturalWidth || src.width, src.naturalHeight || src.height], tileSize: calculateImageTileSize(src) }; IMAGE_CACHE.put(imageSource, imageCache, ref); } if (IMAGE_CACHE.has(imageSource)) { var _imageCache = IMAGE_CACHE.get(imageSource, ref); if (_imageCache.img.complete) { return Promise.resolve(_imageCache); } return new Promise(function (resolve, reject) { _imageCache.img.addEventListener('load', function () { _imageCache.size = [_imageCache.img.naturalWidth || _imageCache.img.width, _imageCache.img.naturalHeight || _imageCache.img.height]; _imageCache.tileSize = calculateImageTileSize(_imageCache.img); resolve(_imageCache); }); _imageCache.img.addEventListener('error', function (ev) { reject(ev); }); }); } return new Promise(function (resolve, reject) { // @see https://github.com/antvis/g/issues/938 var image = _this2.context.config.createImage(); if (image) { var _imageCache2 = { img: image, size: [0, 0], tileSize: calculateImageTileSize(image) }; IMAGE_CACHE.put(imageSource, _imageCache2, ref); image.onload = function () { _imageCache2.size = [image.naturalWidth || image.width, image.naturalHeight || image.height]; _imageCache2.tileSize = calculateImageTileSize(_imageCache2.img); resolve(_imageCache2); }; image.onerror = function (ev) { reject(ev); }; image.crossOrigin = 'Anonymous'; image.src = imageSource; } }); } }, { key: "createDownSampledImage", value: function () { var _createDownSampledImage = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(src, ref) { var imageCache, enableLargeImageOptimization, _ref, _ref$maxDownSampledIm, maxDownSampledImageSize, _ref$downSamplingRate, downSamplingRateThreshold, createImageBitmapFunc, _imageCache$size, originWidth, originHeight, resizedImage, downSamplingRate, updateCache; return _regeneratorRuntime().wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: _context.next = 1; return this.getOrCreateImage(src, ref); case 1: imageCache = _context.sent; if (!(typeof imageCache.downSamplingRate !== 'undefined')) { _context.next = 2; break; } return _context.abrupt("return", imageCache); case 2: enableLargeImageOptimization = this.context.config.enableLargeImageOptimization; _ref = typeof enableLargeImageOptimization === 'boolean' ? {} : enableLargeImageOptimization, _ref$maxDownSampledIm = _ref.maxDownSampledImageSize, maxDownSampledImageSize = _ref$maxDownSampledIm === void 0 ? 2048 : _ref$maxDownSampledIm, _ref$downSamplingRate = _ref.downSamplingRateThreshold, downSamplingRateThreshold = _ref$downSamplingRate === void 0 ? 0.5 : _ref$downSamplingRate; createImageBitmapFunc = this.runtime.globalThis.createImageBitmap; _imageCache$size = _slicedToArray(imageCache.size, 2), originWidth = _imageCache$size[0], originHeight = _imageCache$size[1]; resizedImage = imageCache.img; downSamplingRate = Math.min((maxDownSampledImageSize + maxDownSampledImageSize) / (originWidth + originHeight), Math.max(0.01, Math.min(downSamplingRateThreshold, 0.5))); updateCache = _objectSpread(_objectSpread({}, imageCache), {}, { downSamplingRate: downSamplingRate }); IMAGE_CACHE.update(imageCache.img.src, updateCache, ref); if (!createImageBitmapFunc) { _context.next = 7; break; } _context.prev = 3; _context.next = 4; return createImageBitmapFunc(imageCache.img, { resizeWidth: originWidth * downSamplingRate, resizeHeight: originHeight * downSamplingRate }); case 4: resizedImage = _context.sent; _context.next = 6; break; case 5: _context.prev = 5; _context["catch"](3); downSamplingRate = 1; case 6: _context.next = 8; break; case 7: downSamplingRate = 1; case 8: updateCache = _objectSpread(_objectSpread({}, this.getImageSync(src, ref)), {}, { downSampled: resizedImage, downSamplingRate: downSamplingRate }); IMAGE_CACHE.update(imageCache.img.src, updateCache, ref); return _context.abrupt("return", updateCache); case 9: case "end": return _context.stop(); } }, _callee, this, [[3, 5]]); })); function createDownSampledImage(_x, _x2) { return _createDownSampledImage.apply(this, arguments); } return createDownSampledImage; }() }, { key: "createImageTiles", value: function () { var _createImageTiles = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(src, tiles, rerender, ref) { var imageCache, _ref$ownerDocument$de, requestAnimationFrame, cancelAnimationFrame, updateCache; return _regeneratorRuntime().wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: _context2.next = 1; return this.getOrCreateImage(src, ref); case 1: imageCache = _context2.sent; _ref$ownerDocument$de = ref.ownerDocument.defaultView, requestAnimationFrame = _ref$ownerDocument$de.requestAnimationFrame, cancelAnimationFrame = _ref$ownerDocument$de.cancelAnimationFrame; ImageSlicer.api = { requestAnimationFrame: requestAnimationFrame, cancelAnimationFrame: cancelAnimationFrame, createCanvas: function createCanvas() { return gLite.OffscreenCanvasCreator.createCanvas(); } }; updateCache = _objectSpread(_objectSpread({}, imageCache), ImageSlicer.sliceImage(imageCache.img, imageCache.tileSize[0], imageCache.tileSize[0], rerender)); IMAGE_CACHE.update(imageCache.img.src, updateCache, ref); return _context2.abrupt("return", updateCache); case 2: case "end": return _context2.stop(); } }, _callee2, this); })); function createImageTiles(_x3, _x4, _x5, _x6) { return _createImageTiles.apply(this, arguments); } return createImageTiles; }() }, { key: "releaseImage", value: function releaseImage(src, ref) { IMAGE_CACHE.release(util.isString(src) ? src : src.src, ref); } }, { key: "releaseImageRef", value: function releaseImageRef(ref) { IMAGE_CACHE.releaseRef(ref); } }, { key: "getOrCreatePatternSync", value: function getOrCreatePatternSync(object, pattern, context, $offscreenCanvas, dpr, min, callback) { var patternKey = this.generatePatternKey(pattern); if (patternKey && this.patternCache[patternKey]) { return this.patternCache[patternKey]; } var image = pattern.image, repetition = pattern.repetition, transform = pattern.transform; var src; var needScaleWithDPR = false; // Image URL if (util.isString(image)) { var imageCache = this.getImageSync(image, object, callback); src = imageCache === null || imageCache === void 0 ? void 0 : imageCache.img; } else if ($offscreenCanvas) { src = $offscreenCanvas; needScaleWithDPR = true; } else { src = image; } // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createPattern var canvasPattern = src && context.createPattern(src, repetition); if (canvasPattern) { var mat; // @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern/setTransform if (transform) { mat = gLite.parsedTransformToMat4(gLite.parseTransform(transform), new gLite.DisplayObject({})); } else { mat = glMatrix.mat4.identity(glMatrix.mat4.create()); } if (needScaleWithDPR) { glMatrix.mat4.scale(mat, mat, [1 / dpr, 1 / dpr, 1]); } canvasPattern.setTransform({ a: mat[0], b: mat[1], c: mat[4], d: mat[5], e: mat[12] + min[0], f: mat[13] + min[1] }); } if (patternKey && canvasPattern) { this.patternCache[patternKey] = canvasPattern; } return canvasPattern; } }, { key: "getOrCreateGradient", value: function getOrCreateGradient(params, context) { var key = this.generateGradientKey(params); var type = params.type, steps = params.steps, min = params.min, width = params.width, height = params.height, angle = params.angle, cx = params.cx, cy = params.cy, size = params.size; if (this.gradientCache[key]) { return this.gradientCache[key]; } var gradient = null; if (type === gLite.GradientType.LinearGradient) { var _computeLinearGradien = gLite.computeLinearGradient(min, width, height, angle), x1 = _computeLinearGradien.x1, y1 = _computeLinearGradien.y1, x2 = _computeLinearGradien.x2, y2 = _computeLinearGradien.y2; // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createLinearGradient gradient = context.createLinearGradient(x1, y1, x2, y2); } else if (type === gLite.GradientType.RadialGradient) { var _computeRadialGradien = gLite.computeRadialGradient(min, width, height, cx, cy, size), x = _computeRadialGradien.x, y = _computeRadialGradien.y, r = _computeRadialGradien.r; // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createRadialGradient gradient = context.createRadialGradient(x, y, 0, x, y, r); } if (gradient) { steps.forEach(function (_ref2) { var offset = _ref2.offset, color = _ref2.color; if (offset.unit === gLite.UnitType.kPercentage) { var _gradient; (_gradient = gradient) === null || _gradient === void 0 || _gradient.addColorStop(offset.value / 100, color.toString()); } }); this.gradientCache[key] = gradient; } return this.gradientCache[key]; } }, { key: "generateGradientKey", value: function generateGradientKey(params) { var type = params.type, min = params.min, width = params.width, height = params.height, steps = params.steps, angle = params.angle, cx = params.cx, cy = params.cy, size = params.size; return "gradient-".concat(type, "-").concat((angle === null || angle === void 0 ? void 0 : angle.toString()) || 0, "-").concat((cx === null || cx === void 0 ? void 0 : cx.toString()) || 0, "-").concat((cy === null || cy === void 0 ? void 0 : cy.toString()) || 0, "-").concat((size === null || size === void 0 ? void 0 : size.toString()) || 0, "-").concat(min[0], "-").concat(min[1], "-").concat(width, "-").concat(height, "-").concat(steps.map(function (_ref3) { var offset = _ref3.offset, color = _ref3.color; return "".concat(offset).concat(color); }).join('-')); } }, { key: "generatePatternKey", value: function generatePatternKey(pattern) { var image = pattern.image, repetition = pattern.repetition; // only generate cache for Image if (util.isString(image)) { return "pattern-".concat(image, "-").concat(repetition); } if (image.nodeName === 'rect') { return "pattern-".concat(image.entity, "-").concat(repetition); } } }]); }(); ImagePool.isSupportTile = !!gLite.OffscreenCanvasCreator.createCanvas(); function calculateImageTileSize(img) { if (!img.complete) { return [0, 0]; } var width = img.naturalWidth || img.width, height = img.naturalHeight || img.height; var tileSize = 256; [256, 512].forEach(function (size) { var rows = Math.ceil(height / size); var cols = Math.ceil(width / size); if (rows * cols < 1e3) { tileSize = size; } }); return [tileSize, tileSize]; } var LoadImagePlugin = /*#__PURE__*/function () { function LoadImagePlugin() { _classCallCheck(this, LoadImagePlugin); } return _createClass(LoadImagePlugin, [{ key: "apply", value: function apply(context) { var renderingService = context.renderingService, renderingContext = context.renderingContext, imagePool = context.imagePool; var canvas = renderingContext.root.ownerDocument.defaultView; var calculateWithAspectRatio = function calculateWithAspectRatio(object, imageWidth, imageHeight) { var _object$parsedStyle = object.parsedStyle, width = _object$parsedStyle.width, height = _object$parsedStyle.height; if (width && !height) { object.setAttribute('height', imageHeight / imageWidth * width); } else if (!width && height) { object.setAttribute('width', imageWidth / imageHeight * height); } }; var handleMounted = function handleMounted(e) { var object = e.target; var nodeName = object.nodeName, attributes = object.attributes; if (nodeName === gLite.Shape.IMAGE) { var src = attributes.src, keepAspectRatio = attributes.keepAspectRatio; imagePool.getImageSync(src, object, function (_ref) { var _ref$img = _ref.img, width = _ref$img.width, height = _ref$img.height; if (keepAspectRatio) { calculateWithAspectRatio(object, width, height); } // set dirty rectangle flag object.renderable.dirty = true; renderingService.dirtify(); }); } }; var handleAttributeChanged = function handleAttributeChanged(e) { var object = e.target; var attrName = e.attrName, prevValue = e.prevValue, newValue = e.newValue; if (object.nodeName !== gLite.Shape.IMAGE || attrName !== 'src') { return; } if (prevValue !== newValue) { imagePool.releaseImage(prevValue, object); } if (util.isString(newValue)) { imagePool.getOrCreateImage(newValue, object).then(function (_ref2) { var _ref2$img = _ref2.img, width = _ref2$img.width, height = _ref2$img.height; if (object.attributes.keepAspectRatio) { calculateWithAspectRatio(object, width, height); } // set dirty rectangle flag object.renderable.dirty = true; renderingService.dirtify(); })["catch"](function () { // }); } }; renderingService.hooks.init.tap(LoadImagePlugin.tag, function () { canvas.addEventListener(gLite.ElementEvent.MOUNTED, handleMounted); canvas.addEventListener(gLite.ElementEvent.ATTR_MODIFIED, handleAttributeChanged); }); renderingService.hooks.destroy.tap(LoadImagePlugin.tag, function () { canvas.removeEventListener(gLite.ElementEvent.MOUNTED, handleMounted); canvas.removeEventListener(gLite.ElementEvent.ATTR_MODIFIED, handleAttributeChanged); }); } }]); }(); LoadImagePlugin.tag = 'LoadImage'; var Plugin = /*#__PURE__*/function (_AbstractRendererPlug) { function Plugin() { var _this; _classCallCheck(this, Plugin); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _callSuper(this, Plugin, [].concat(args)); _this.name = 'image-loader'; return _this; } _inherits(Plugin, _AbstractRendererPlug); return _createClass(Plugin, [{ key: "init", value: function init(runtime) { // @ts-ignore this.context.imagePool = new ImagePool(this.context, runtime); this.addRenderingPlugin(new LoadImagePlugin()); } }, { key: "destroy", value: function destroy() { this.removeAllRenderingPlugins(); } }]); }(gLite.AbstractRendererPlugin); exports.ImagePool = ImagePool; exports.Plugin = Plugin; //# sourceMappingURL=index.js.map