index.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import canvastoDataURL from './canvastoDataURL';
  2. import canvastoFile from './canvastoFile';
  3. import dataURLtoFile from './dataURLtoFile';
  4. import dataURLtoImage from './dataURLtoImage';
  5. import downloadFile from './downloadFile';
  6. import filetoDataURL from './filetoDataURL';
  7. import imagetoCanvas from './imagetoCanvas';
  8. import urltoBlob from './urltoBlob';
  9. import urltoImage from './urltoImage';
  10. import { checkImageType } from '@utils';
  11. import { EImageType, ICompressConfig, compressAccuratelyConfig } from '@models';
  12. /**
  13. * 压缩File(Blob)对象
  14. * @param {Blob} file - 一个File(Blob)对象
  15. * @param {(number|object)} config - 如果传入是number类型,传入范围 0-1,表示图片压缩质量,默认0.92;也可以传入object类型,以便更详细的配置
  16. * @example
  17. * imageConversion.compress(file,0.8)
  18. *
  19. * imageConversion.compress(file,{
  20. * quality: 0.8, //图片压缩质量
  21. * type:"image/png", //转换后的图片类型,选项有 "image/png", "image/jpeg", "image/gif"
  22. * width: 300, //生成图片的宽度
  23. * height:200, //生产图片的高度
  24. * scale: 0.5, //相对于原始图片的缩放比率,设置config.scale后会覆盖config.width和config.height的设置;
  25. * orientation:2, //图片旋转方向
  26. * })
  27. *
  28. * @returns {Promise(Blob)}
  29. */
  30. async function compress(file: File, config: ICompressConfig = {}): Promise<Blob> {
  31. if (!(file instanceof Blob)) {
  32. throw new Error('compress(): First arg must be a Blob object or a File object.');
  33. }
  34. if (typeof config !== 'object') {
  35. config = Object.assign({
  36. quality: config,
  37. });
  38. }
  39. config.quality = Number(config.quality);
  40. if (Number.isNaN(config.quality)) {
  41. return file;
  42. }
  43. const dataURL = await filetoDataURL(file);
  44. let originalMime = dataURL.split(',')[0].match(/:(.*?);/)[1] as EImageType; // 原始图像图片类型
  45. let mime = EImageType.JPEG; // 默认压缩类型
  46. if (checkImageType(config.type)) {
  47. mime = config.type;
  48. originalMime = config.type;
  49. }
  50. const image = await dataURLtoImage(dataURL);
  51. const canvas = await imagetoCanvas(image, Object.assign({}, config));
  52. const compressDataURL = await canvastoDataURL(canvas, config.quality, mime);
  53. const compressFile = await dataURLtoFile(compressDataURL, originalMime);
  54. if (compressFile.size > file.size) {
  55. return file;
  56. }
  57. return compressFile;
  58. };
  59. /**
  60. * 根据体积压缩File(Blob)对象
  61. *
  62. * @param {Blob} file - 一个File(Blob)对象
  63. * @param {(number|object)} config - 如果传入是number类型,则指定压缩图片的体积,单位Kb;也可以传入object类型,以便更详细的配置
  64. * @param {number} size - 指定压缩图片的体积,单位Kb
  65. * @param {number} accuracy - 相对于指定压缩体积的精确度,范围0.8-0.99,默认0.95;
  66. * 如果设置 图片体积1000Kb,精确度0.9,则压缩结果为900Kb-1100Kb的图片都算合格;
  67. * @example
  68. * imageConversion.compress(file,100) //压缩后图片大小为100kb
  69. *
  70. * imageConversion.compress(file,{
  71. * size: 100, //图片压缩体积,单位Kb
  72. * accuracy: 0.9, //图片压缩体积的精确度,默认0.95
  73. * type:"image/png", //转换后的图片类型,选项有 "image/png", "image/jpeg", "image/gif"
  74. * width: 300, //生成图片的宽度
  75. * height: 200, //生产图片的高度
  76. * scale: 0.5, //相对于原始图片的缩放比率,设置config.scale后会覆盖config.width和config.height的设置;
  77. * orientation:2, //图片旋转方向
  78. * })
  79. *
  80. * @returns {Promise(Blob)}
  81. */
  82. async function compressAccurately(file: Blob, config: compressAccuratelyConfig = {}): Promise<Blob> {
  83. if (!(file instanceof Blob)) {
  84. throw new Error('compressAccurately(): First arg must be a Blob object or a File object.');
  85. }
  86. if (typeof config !== 'object') {
  87. config = Object.assign({
  88. size: config,
  89. });
  90. }
  91. // 如果指定体积不是数字或者数字字符串,则不做处理
  92. config.size = Number(config.size);
  93. if (Number.isNaN(config.size)) {
  94. return file;
  95. }
  96. // 如果指定体积大于原文件体积,则不做处理;
  97. if (config.size * 1024 > file.size) {
  98. return file;
  99. }
  100. config.accuracy = Number(config.accuracy);
  101. if (!config.accuracy
  102. || config.accuracy < 0.8
  103. || config.accuracy > 0.99) {
  104. config.accuracy = 0.95; // 默认精度0.95
  105. }
  106. const resultSize = {
  107. max: config.size * (2 - config.accuracy) * 1024,
  108. accurate: config.size * 1024,
  109. min: config.size * config.accuracy * 1024,
  110. };
  111. const dataURL = await filetoDataURL(file);
  112. let originalMime = dataURL.split(',')[0].match(/:(.*?);/)[1] as EImageType; // 原始图像图片类型
  113. let mime = EImageType.JPEG;
  114. if (checkImageType(config.type)) {
  115. mime = config.type;
  116. originalMime = config.type;
  117. }
  118. const image = await dataURLtoImage(dataURL);
  119. const canvas = await imagetoCanvas(image, Object.assign({}, config));
  120. /**
  121. * 经过测试发现,blob.size与dataURL.length的比值约等于0.75
  122. * 这个比值可以同过dataURLtoFile这个方法来测试验证
  123. * 这里为了提高性能,直接通过这个比值来计算出blob.size
  124. */
  125. const proportion = 0.75;
  126. let imageQuality = 0.5;
  127. let compressDataURL;
  128. const tempDataURLs: string[] = [null, null];
  129. /**
  130. * HTMLCanvasElement.toBlob()以及HTMLCanvasElement.toDataURL()压缩参数
  131. * 的最小细粒度为0.01,而2的7次方为128,即只要循环7次,则会覆盖所有可能性
  132. */
  133. for (let x = 1; x <= 7; x++) {
  134. compressDataURL = await canvastoDataURL(canvas, imageQuality, mime);
  135. const CalculationSize = compressDataURL.length * proportion;
  136. // 如果到循环第七次还没有达到精确度的值,那说明该图片不能达到到此精确度要求
  137. // 这时候最后一次循环出来的dataURL可能不是最精确的,需要取其周边两个dataURL三者比较来选出最精确的;
  138. if (x === 7) {
  139. if (resultSize.max < CalculationSize || resultSize.min > CalculationSize) {
  140. compressDataURL = [compressDataURL, ...tempDataURLs]
  141. .filter(i => i) // 去除null
  142. .sort((a, b) => Math.abs(a.length * proportion - resultSize.accurate)
  143. - Math.abs(b.length * proportion - resultSize.accurate))[0];
  144. }
  145. break;
  146. }
  147. if (resultSize.max < CalculationSize) {
  148. tempDataURLs[1] = compressDataURL;
  149. imageQuality -= 0.5 ** (x + 1);
  150. } else if (resultSize.min > CalculationSize) {
  151. tempDataURLs[0] = compressDataURL;
  152. imageQuality += 0.5 ** (x + 1);
  153. } else {
  154. break;
  155. }
  156. }
  157. const compressFile = await dataURLtoFile(compressDataURL, originalMime);
  158. // 如果压缩后体积大于原文件体积,则返回源文件;
  159. if (compressFile.size > file.size) {
  160. return file;
  161. }
  162. return compressFile;
  163. };
  164. export {
  165. canvastoDataURL,
  166. canvastoFile,
  167. dataURLtoFile,
  168. dataURLtoImage,
  169. downloadFile,
  170. filetoDataURL,
  171. imagetoCanvas,
  172. urltoBlob,
  173. urltoImage,
  174. compress,
  175. compressAccurately
  176. };
  177. export {
  178. EImageType
  179. };