parse-image.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import { getError, gfx, SpriteFrame } from "cc";
  2. import { XIAOMI } from "cc/env";
  3. /**
  4. * @en The image source, can be HTML canvas, image type or image in memory data
  5. * @zh 图像资源的原始图像源。可以来源于 HTML 元素也可以来源于内存。
  6. */
  7. type ImageSource = HTMLCanvasElement | HTMLImageElement | ImageBitmap; // 引擎解析到内存中可用的图像资源类型
  8. /**
  9. * @en The task for decryption
  10. * @zh 解密任务
  11. */
  12. type DecryptionTask = {
  13. name: string;
  14. image: HTMLImageElement;
  15. imgdata: ImageData;
  16. complete_call: (source: ImageSource)=>void;
  17. };
  18. const ccwindow: typeof window = typeof globalThis.jsb !== 'undefined' ? (typeof jsb.window !== 'undefined' ? jsb.window : globalThis) : globalThis;
  19. /** 解密序列,要与加密脚本中的设置保持一致 */
  20. const decryption_order = '3580419267'.split('').map(Number);
  21. /**
  22. * 解析图片
  23. * @param file
  24. * @param options
  25. * @param onComplete
  26. */
  27. export function parseImage(file: string|HTMLImageElement, options: any, onComplete: (err: any, img: ImageSource)=>void){
  28. if(task_list.length<=0){
  29. task_start_time = Date.now();
  30. }
  31. if(file){
  32. let start_time = Date.now();
  33. if(typeof file === 'string'){ // 一般原生环境中,并不直接传入图片资源,而是传入图片路径
  34. loadImage(file, (err, img)=>{
  35. let file_name = file.split('/').pop();
  36. logInfo('LoadImage', task_list.length, file_name, Date.now()-start_time);
  37. if(err){
  38. onComplete(err, null);
  39. }else{
  40. decryptionImage(file_name, img, source=>{
  41. onComplete(null, source);
  42. });
  43. }
  44. });
  45. }else if(file instanceof HTMLImageElement){ // 在浏览器环境中,直接传入图片资源
  46. let file_name = file.src.split('/').pop();
  47. logInfo('LoadImage', task_list.length, file_name, Date.now()-start_time);
  48. decryptionImage(file_name, file, source=>{
  49. onComplete(null, source);
  50. });
  51. }else{
  52. onComplete(getError(4930, file), null);
  53. }
  54. }else{
  55. // @ts-ignore
  56. onComplete(null, file);
  57. }
  58. }
  59. /**
  60. * 加载图片
  61. * @param url HTMLImageElement.src
  62. */
  63. function loadImage(url: string, loaddone: (err: any, image: HTMLImageElement)=>void){
  64. let img = new ccwindow.Image();
  65. if (ccwindow.location.protocol !== 'file:' || XIAOMI) {
  66. img.crossOrigin = 'anonymous';
  67. }
  68. function loadCallback() {
  69. img.removeEventListener('load', loadCallback);
  70. img.removeEventListener('error', errorCallback);
  71. loaddone(null, img);
  72. }
  73. function errorCallback() {
  74. img.removeEventListener('load', loadCallback);
  75. img.removeEventListener('error', errorCallback);
  76. loaddone(new Error(getError(4930, url)), null);
  77. }
  78. img.addEventListener('load', loadCallback);
  79. img.addEventListener('error', errorCallback);
  80. img.src = url;
  81. }
  82. /** 获取图片数据的离屏画布上下文对象 */
  83. var context2d_for_image_data: CanvasRenderingContext2D;
  84. /**
  85. * 获取图片数据
  86. * @param image
  87. */
  88. function getImageData(image: HTMLImageElement){
  89. if(!context2d_for_image_data){
  90. context2d_for_image_data = document.createElement('canvas').getContext('2d');
  91. }
  92. // 创建一个精灵帧
  93. let spf = SpriteFrame.createWithImage(image);
  94. // 创建一个缓冲区
  95. let buffer = new Uint8Array(image.width * image.height * 4);
  96. // 创建一个区域描述对象
  97. let region = new gfx.BufferTextureCopy();
  98. region.texOffset.x = 0;
  99. region.texOffset.y = 0;
  100. region.texExtent.width = image.width;
  101. region.texExtent.height = image.height;
  102. // 将纹理拷贝到缓冲区
  103. gfx.deviceManager.gfxDevice.copyTextureToBuffers(spf.getGFXTexture(), [buffer], [region]);
  104. // 创建一个ImageData对象
  105. let imageData = context2d_for_image_data.createImageData(image.width, image.height);
  106. // 将缓冲区数据拷贝到ImageData对象
  107. imageData.data.set(buffer);
  108. return imageData;
  109. }
  110. /**
  111. * 通过一个ImageData对象创建一个canvas资源
  112. * @param data
  113. * @param width
  114. * @param height
  115. */
  116. function createCanvasSource(data: ImageData, width: number, height: number){
  117. let canvas = ccwindow.document.createElement('canvas');
  118. canvas.width = width;
  119. canvas.height = height;
  120. let ctx = canvas.getContext('2d');
  121. ctx.putImageData(data, 0, 0);
  122. return canvas;
  123. }
  124. /**
  125. * 解密图片
  126. * @param name
  127. * @param image
  128. * @param complete_call
  129. */
  130. function decryptionImage(name: string, image: HTMLImageElement, complete_call: (source: ImageSource)=>void){
  131. runTask({ name, image, imgdata: null, complete_call });
  132. }
  133. /** 解密任务列表 */
  134. const task_list: DecryptionTask[] = [];
  135. /** 解析完成的任务数量 */
  136. var idx_decrypt = 0;
  137. /** 资源创建完成的任务数量 */
  138. var idx_create = 0;
  139. /** 解析任务执行状态 */
  140. var ing_decrypt = false;
  141. /** 创建资源任务状态 */
  142. var ing_create = false;
  143. var task_start_time = 0;
  144. /**
  145. * 添加任务,并执行
  146. * @param task
  147. */
  148. function runTask(task: DecryptionTask){
  149. task_list.push(task);
  150. let start_time = Date.now();
  151. task.imgdata = getImageData(task.image);
  152. logInfo('GetImageData', task_list.length-1, task.name, Date.now()-start_time);
  153. runDecryption();
  154. }
  155. /** 执行解密任务 */
  156. function runDecryption(){
  157. if(ing_decrypt || idx_decrypt>=task_list.length){
  158. return void 0;
  159. }
  160. ing_decrypt = true;
  161. while(idx_decrypt < task_list.length){
  162. let task = task_list[idx_decrypt];
  163. let start_time = Date.now();
  164. let cut_step = Math.max(1, getUnderMaxPrime(Math.min(task.image.width, task.image.height)));
  165. decryptionBuffer(task.imgdata.data, decryption_order, cut_step);
  166. logInfo('Decryption', idx_decrypt, task.name, Date.now()-start_time);
  167. idx_decrypt++;
  168. }
  169. ing_decrypt = false;
  170. runCreateSource();
  171. }
  172. /** 执行创建资源任务 */
  173. function runCreateSource(){
  174. if(ing_create || idx_create>=idx_decrypt){
  175. return void 0;
  176. }
  177. ing_create = true;
  178. while(idx_create < idx_decrypt){
  179. let task = task_list[idx_create];
  180. let start_time = Date.now();
  181. task.complete_call(createCanvasSource(task.imgdata, task.image.width, task.image.height));
  182. logInfo('CreateSource', idx_create, task.name, Date.now()-start_time);
  183. idx_create++;
  184. }
  185. ing_create = false;
  186. if(idx_create>=task_list.length){
  187. logInfo('CurrtPartTask', idx_create, 'Done', Date.now()-task_start_time);
  188. }
  189. }
  190. /** 解密缓冲区 */
  191. var cache_buffer: Uint8Array|Uint8ClampedArray;
  192. /**
  193. * 解密缓冲区
  194. * @param buffer
  195. * @param order
  196. * @param cut_length
  197. */
  198. function decryptionBuffer(buffer: Uint8Array|Uint8ClampedArray, order: number[], cut_length: number){
  199. if(cut_length>=buffer.length || !(cut_length>0)){
  200. return buffer;
  201. }
  202. if(!cache_buffer || !(cache_buffer.length>=buffer.length)){
  203. cache_buffer = new Uint8Array(buffer.length);
  204. }
  205. let cut_count = Math.floor(buffer.length / cut_length);
  206. let idx_map_range: number;
  207. let idx_map: number[][];
  208. let offset = 0;
  209. do{
  210. idx_map = createDecryptionIdxMap(order, cut_count-offset);
  211. idx_map_range = idx_map[0].length * Math.pow(10, idx_map.length-1);
  212. for(let i = 0; i < idx_map_range; i++){
  213. let idx = (offset + i) * cut_length;
  214. let trans_idx = (offset + transformIndex(i, idx_map)) * cut_length;
  215. cache_buffer.set(buffer.slice(idx, idx+cut_length), trans_idx);
  216. }
  217. offset += idx_map_range;
  218. }while(offset < cut_count);
  219. buffer.set(cache_buffer.slice(0, cut_count * cut_length));
  220. return buffer;
  221. }
  222. /**
  223. * 转换索引
  224. * @param index
  225. * @param trans_map
  226. */
  227. function transformIndex(index: number, trans_map: number[][]){
  228. let list = index.toString().padStart(trans_map.length, '0').split('').map(el=>parseInt(el));
  229. let str = list.map((el, i)=>trans_map[i][el]).join('');
  230. return parseInt(str);
  231. }
  232. /**
  233. * 创建解密索引映射
  234. * @param length
  235. * @param order
  236. */
  237. function createDecryptionIdxMap(order: number[], length: number){
  238. let index_map = length.toString().split('')
  239. .map((el, idx)=>{
  240. let result: number[] = [];
  241. (new Array(idx==0 ? parseInt(el) : order.length))
  242. .fill(0).map((_, i)=>i)
  243. .sort((a, b)=>order.indexOf(a) - order.indexOf(b))
  244. .forEach((e, i)=> result[e] = i);
  245. return result;
  246. });
  247. return index_map;
  248. }
  249. /**
  250. * 获取小于指定值的最大素数
  251. * @param num
  252. */
  253. function getUnderMaxPrime(num: number){
  254. let value = num;
  255. let prime = Math.min(value, findMaxPrime(value));
  256. while(prime >= num){
  257. value = value-1;
  258. prime = Math.min(value, findMaxPrime(value));
  259. }
  260. return prime;
  261. }
  262. /**
  263. * 查找最大素数
  264. * @param num
  265. */
  266. function findMaxPrime(num: number){
  267. let max_prime = num;
  268. if(max_prime > 3){
  269. for(let i=max_prime; i>2; i--){
  270. let is_prime = true;
  271. for(let j=2; j*j<=i; j++){
  272. if(i % j == 0){
  273. is_prime = false;
  274. break;
  275. }
  276. }
  277. if(is_prime){
  278. max_prime = i;
  279. break;
  280. }
  281. }
  282. }else{
  283. max_prime = Math.max(2, max_prime);
  284. }
  285. return max_prime;
  286. }
  287. function logInfo(tag: string, index: number, name: string, time?: number){
  288. tag = '#' + index.toString().padStart(3, '0') + '@' + tag.padEnd(13, ' ') + '>>';
  289. if(time){
  290. tag += '|' + formatTime(time) + '|';
  291. }
  292. console.log(tag, name);
  293. }
  294. function formatTime(time: number){
  295. let s = Math.floor(time / 1000);
  296. let m = Math.floor(s / 60);
  297. let h = Math.floor(m / 60);
  298. return [h, m, s].map(v=>v.toString().padStart(2, '0')).join(':') + ' ' + (time % 1000).toString().padStart(3, '0') + 'ms';
  299. }