import { getError, gfx, SpriteFrame } from "cc"; import { XIAOMI } from "cc/env"; /** * @en The image source, can be HTML canvas, image type or image in memory data * @zh 图像资源的原始图像源。可以来源于 HTML 元素也可以来源于内存。 */ type ImageSource = HTMLCanvasElement | HTMLImageElement | ImageBitmap; // 引擎解析到内存中可用的图像资源类型 /** * @en The task for decryption * @zh 解密任务 */ type DecryptionTask = { name: string; image: HTMLImageElement; imgdata: ImageData; complete_call: (source: ImageSource)=>void; }; const ccwindow: typeof window = typeof globalThis.jsb !== 'undefined' ? (typeof jsb.window !== 'undefined' ? jsb.window : globalThis) : globalThis; /** 解密序列,要与加密脚本中的设置保持一致 */ const decryption_order = '3580419267'.split('').map(Number); /** * 解析图片 * @param file * @param options * @param onComplete */ export function parseImage(file: string|HTMLImageElement, options: any, onComplete: (err: any, img: ImageSource)=>void){ if(task_list.length<=0){ task_start_time = Date.now(); } if(file){ let start_time = Date.now(); if(typeof file === 'string'){ // 一般原生环境中,并不直接传入图片资源,而是传入图片路径 loadImage(file, (err, img)=>{ let file_name = file.split('/').pop(); logInfo('LoadImage', task_list.length, file_name, Date.now()-start_time); if(err){ onComplete(err, null); }else{ decryptionImage(file_name, img, source=>{ onComplete(null, source); }); } }); }else if(file instanceof HTMLImageElement){ // 在浏览器环境中,直接传入图片资源 let file_name = file.src.split('/').pop(); logInfo('LoadImage', task_list.length, file_name, Date.now()-start_time); decryptionImage(file_name, file, source=>{ onComplete(null, source); }); }else{ onComplete(getError(4930, file), null); } }else{ // @ts-ignore onComplete(null, file); } } /** * 加载图片 * @param url HTMLImageElement.src */ function loadImage(url: string, loaddone: (err: any, image: HTMLImageElement)=>void){ let img = new ccwindow.Image(); if (ccwindow.location.protocol !== 'file:' || XIAOMI) { img.crossOrigin = 'anonymous'; } function loadCallback() { img.removeEventListener('load', loadCallback); img.removeEventListener('error', errorCallback); loaddone(null, img); } function errorCallback() { img.removeEventListener('load', loadCallback); img.removeEventListener('error', errorCallback); loaddone(new Error(getError(4930, url)), null); } img.addEventListener('load', loadCallback); img.addEventListener('error', errorCallback); img.src = url; } /** 获取图片数据的离屏画布上下文对象 */ var context2d_for_image_data: CanvasRenderingContext2D; /** * 获取图片数据 * @param image */ function getImageData(image: HTMLImageElement){ if(!context2d_for_image_data){ context2d_for_image_data = document.createElement('canvas').getContext('2d'); } // 创建一个精灵帧 let spf = SpriteFrame.createWithImage(image); // 创建一个缓冲区 let buffer = new Uint8Array(image.width * image.height * 4); // 创建一个区域描述对象 let region = new gfx.BufferTextureCopy(); region.texOffset.x = 0; region.texOffset.y = 0; region.texExtent.width = image.width; region.texExtent.height = image.height; // 将纹理拷贝到缓冲区 gfx.deviceManager.gfxDevice.copyTextureToBuffers(spf.getGFXTexture(), [buffer], [region]); // 创建一个ImageData对象 let imageData = context2d_for_image_data.createImageData(image.width, image.height); // 将缓冲区数据拷贝到ImageData对象 imageData.data.set(buffer); return imageData; } /** * 通过一个ImageData对象创建一个canvas资源 * @param data * @param width * @param height */ function createCanvasSource(data: ImageData, width: number, height: number){ let canvas = ccwindow.document.createElement('canvas'); canvas.width = width; canvas.height = height; let ctx = canvas.getContext('2d'); ctx.putImageData(data, 0, 0); return canvas; } /** * 解密图片 * @param name * @param image * @param complete_call */ function decryptionImage(name: string, image: HTMLImageElement, complete_call: (source: ImageSource)=>void){ runTask({ name, image, imgdata: null, complete_call }); } /** 解密任务列表 */ const task_list: DecryptionTask[] = []; /** 解析完成的任务数量 */ var idx_decrypt = 0; /** 资源创建完成的任务数量 */ var idx_create = 0; /** 解析任务执行状态 */ var ing_decrypt = false; /** 创建资源任务状态 */ var ing_create = false; var task_start_time = 0; /** * 添加任务,并执行 * @param task */ function runTask(task: DecryptionTask){ task_list.push(task); let start_time = Date.now(); task.imgdata = getImageData(task.image); logInfo('GetImageData', task_list.length-1, task.name, Date.now()-start_time); runDecryption(); } /** 执行解密任务 */ function runDecryption(){ if(ing_decrypt || idx_decrypt>=task_list.length){ return void 0; } ing_decrypt = true; while(idx_decrypt < task_list.length){ let task = task_list[idx_decrypt]; let start_time = Date.now(); let cut_step = Math.max(1, getUnderMaxPrime(Math.min(task.image.width, task.image.height))); decryptionBuffer(task.imgdata.data, decryption_order, cut_step); logInfo('Decryption', idx_decrypt, task.name, Date.now()-start_time); idx_decrypt++; } ing_decrypt = false; runCreateSource(); } /** 执行创建资源任务 */ function runCreateSource(){ if(ing_create || idx_create>=idx_decrypt){ return void 0; } ing_create = true; while(idx_create < idx_decrypt){ let task = task_list[idx_create]; let start_time = Date.now(); task.complete_call(createCanvasSource(task.imgdata, task.image.width, task.image.height)); logInfo('CreateSource', idx_create, task.name, Date.now()-start_time); idx_create++; } ing_create = false; if(idx_create>=task_list.length){ logInfo('CurrtPartTask', idx_create, 'Done', Date.now()-task_start_time); } } /** 解密缓冲区 */ var cache_buffer: Uint8Array|Uint8ClampedArray; /** * 解密缓冲区 * @param buffer * @param order * @param cut_length */ function decryptionBuffer(buffer: Uint8Array|Uint8ClampedArray, order: number[], cut_length: number){ if(cut_length>=buffer.length || !(cut_length>0)){ return buffer; } if(!cache_buffer || !(cache_buffer.length>=buffer.length)){ cache_buffer = new Uint8Array(buffer.length); } let cut_count = Math.floor(buffer.length / cut_length); let idx_map_range: number; let idx_map: number[][]; let offset = 0; do{ idx_map = createDecryptionIdxMap(order, cut_count-offset); idx_map_range = idx_map[0].length * Math.pow(10, idx_map.length-1); for(let i = 0; i < idx_map_range; i++){ let idx = (offset + i) * cut_length; let trans_idx = (offset + transformIndex(i, idx_map)) * cut_length; cache_buffer.set(buffer.slice(idx, idx+cut_length), trans_idx); } offset += idx_map_range; }while(offset < cut_count); buffer.set(cache_buffer.slice(0, cut_count * cut_length)); return buffer; } /** * 转换索引 * @param index * @param trans_map */ function transformIndex(index: number, trans_map: number[][]){ let list = index.toString().padStart(trans_map.length, '0').split('').map(el=>parseInt(el)); let str = list.map((el, i)=>trans_map[i][el]).join(''); return parseInt(str); } /** * 创建解密索引映射 * @param length * @param order */ function createDecryptionIdxMap(order: number[], length: number){ let index_map = length.toString().split('') .map((el, idx)=>{ let result: number[] = []; (new Array(idx==0 ? parseInt(el) : order.length)) .fill(0).map((_, i)=>i) .sort((a, b)=>order.indexOf(a) - order.indexOf(b)) .forEach((e, i)=> result[e] = i); return result; }); return index_map; } /** * 获取小于指定值的最大素数 * @param num */ function getUnderMaxPrime(num: number){ let value = num; let prime = Math.min(value, findMaxPrime(value)); while(prime >= num){ value = value-1; prime = Math.min(value, findMaxPrime(value)); } return prime; } /** * 查找最大素数 * @param num */ function findMaxPrime(num: number){ let max_prime = num; if(max_prime > 3){ for(let i=max_prime; i>2; i--){ let is_prime = true; for(let j=2; j*j<=i; j++){ if(i % j == 0){ is_prime = false; break; } } if(is_prime){ max_prime = i; break; } } }else{ max_prime = Math.max(2, max_prime); } return max_prime; } function logInfo(tag: string, index: number, name: string, time?: number){ tag = '#' + index.toString().padStart(3, '0') + '@' + tag.padEnd(13, ' ') + '>>'; if(time){ tag += '|' + formatTime(time) + '|'; } console.log(tag, name); } function formatTime(time: number){ let s = Math.floor(time / 1000); let m = Math.floor(s / 60); let h = Math.floor(m / 60); return [h, m, s].map(v=>v.toString().padStart(2, '0')).join(':') + ' ' + (time % 1000).toString().padStart(3, '0') + 'ms'; }