api.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import gm from 'gm';
  2. import axios from 'axios';
  3. import sizeOf from 'image-size';
  4. import path from 'node:path';
  5. import fs from 'node:fs';
  6. import PDFDocument from 'pdfkit';
  7. import log from 'electron-log/renderer';
  8. import {
  9. getImagicPath,
  10. getTempPath,
  11. makeDirSync,
  12. getGmFontPath,
  13. getConfigData,
  14. } from './utils';
  15. import {
  16. DrawTrackItem,
  17. DrawTrackCircleOption,
  18. DrawTrackLineOption,
  19. DrawTrackTextOption,
  20. } from './types';
  21. // macos install gm imagemagick https://github.com/aheckmann/gm/blob/master/README.md
  22. const gmInst =
  23. process.platform === 'win32'
  24. ? gm.subClass({
  25. imageMagick: '7+',
  26. appPath: getImagicPath(),
  27. })
  28. : gm.subClass({ imageMagick: '7+' });
  29. function cropImage(imgPath: string): Promise<string> {
  30. return new Promise((resolve, reject) => {
  31. const outpath = path.join(getTempPath(), '001.png');
  32. gmInst(imgPath)
  33. .crop(500, 200, 0, 0)
  34. .write(outpath, (err) => {
  35. if (!err) {
  36. return resolve(outpath);
  37. }
  38. return reject(err);
  39. });
  40. });
  41. }
  42. function writeData(content: string) {
  43. const outpath = path.join(getTempPath(), 'task.txt');
  44. fs.writeFileSync(outpath, content, { flag: 'a' });
  45. }
  46. function drawTrack(
  47. imgPath: string,
  48. drawTrackList: DrawTrackItem[],
  49. outpath: string
  50. ): Promise<string> {
  51. return new Promise((resolve, reject) => {
  52. const bname = path.basename(outpath);
  53. log.info(`开始绘制:${bname}`);
  54. const gmObj = gmInst(imgPath);
  55. makeDirSync(path.dirname(outpath));
  56. const defaultColor = '#f53f3f';
  57. const defaultFontSize = 22;
  58. gmObj.font(getGmFontPath());
  59. drawTrackList.forEach((track) => {
  60. // text
  61. if (track.type === 'text') {
  62. const { x, y, text, color, fontSize } =
  63. track.option as DrawTrackTextOption;
  64. const fsize = fontSize || defaultFontSize;
  65. const textColor = color || defaultColor;
  66. const ny = y + fsize;
  67. gmObj
  68. .stroke(textColor, 1)
  69. .fill(textColor)
  70. .fontSize(fsize)
  71. .drawText(x, ny, text);
  72. return;
  73. }
  74. // circle
  75. if (track.type === 'circle') {
  76. const { x0, y0, x1, y1 } = track.option as DrawTrackCircleOption;
  77. const rx = (x1 - x0) / 2;
  78. const ry = (y1 - y0) / 2;
  79. const cx = x0 + rx;
  80. const cy = y0 + ry;
  81. gmObj
  82. .stroke(defaultColor, 2)
  83. .fill('none')
  84. .drawEllipse(cx, cy, rx, ry, 0, 360);
  85. return;
  86. }
  87. // line
  88. if (track.type === 'line') {
  89. const { x0, y0, x1, y1 } = track.option as DrawTrackLineOption;
  90. gmObj.stroke(defaultColor, 2).fill('none').drawLine(x0, y0, x1, y1);
  91. }
  92. });
  93. gmObj.write(outpath, (err) => {
  94. log.info(`绘制完成:${bname}`);
  95. if (!err) {
  96. return resolve(outpath);
  97. }
  98. return reject(err);
  99. });
  100. });
  101. }
  102. interface ImageItem {
  103. url: string;
  104. width: number;
  105. height: number;
  106. }
  107. interface TaskItem {
  108. imgItem: ImageItem;
  109. outpath: string;
  110. drawTrackList: DrawTrackItem[];
  111. }
  112. async function drawTracks(taskList: TaskItem[]): Promise<ImageItem[]> {
  113. const downloads = taskList.map((item) =>
  114. drawTrack(item.imgItem.url, item.drawTrackList, item.outpath)
  115. );
  116. const images = await Promise.all(downloads).catch((error) => {
  117. console.log(error);
  118. });
  119. if (!images) {
  120. return Promise.reject(new Error('绘制轨迹错误'));
  121. }
  122. const result = taskList.map((item, index) => {
  123. return {
  124. url: images[index],
  125. width: item.imgItem.width,
  126. height: item.imgItem.height,
  127. };
  128. });
  129. return result;
  130. }
  131. async function downloadFile(url: string, outputPath: string): Promise<string> {
  132. return new Promise((resolve, reject) => {
  133. axios({
  134. url,
  135. method: 'GET',
  136. responseType: 'arraybuffer',
  137. })
  138. .then((response) => {
  139. fs.writeFileSync(outputPath, Buffer.from(response.data, 'binary'));
  140. resolve(outputPath);
  141. })
  142. .catch(() => {
  143. reject();
  144. });
  145. });
  146. }
  147. async function downloadImage(url: string, outputPath: string) {
  148. makeDirSync(path.dirname(outputPath));
  149. await downloadFile(url, outputPath);
  150. const size = sizeOf(outputPath);
  151. return {
  152. url: outputPath,
  153. width: size.width || 100,
  154. height: size.height || 100,
  155. };
  156. }
  157. function joinPath(paths: string[]) {
  158. return path.join(...paths);
  159. }
  160. async function imagesToPdf(
  161. images: ImageItem[],
  162. outpath: string
  163. ): Promise<string> {
  164. return new Promise((resolve, reject) => {
  165. const doc = new PDFDocument({ autoFirstPage: false });
  166. makeDirSync(path.dirname(outpath));
  167. const steam = fs.createWriteStream(outpath);
  168. doc.pipe(steam);
  169. images.forEach((image) => {
  170. const { url, width, height } = image;
  171. doc.addPage({ size: [width, height] });
  172. doc.image(url, 0, 0, { width, height });
  173. });
  174. doc.end();
  175. steam.on('finish', () => {
  176. resolve(outpath);
  177. });
  178. steam.on('error', (err) => {
  179. reject(err);
  180. });
  181. });
  182. }
  183. function logger(content: string, type?: 'info' | 'error') {
  184. if (type === 'error') {
  185. log.error(content);
  186. } else {
  187. log.info(content);
  188. }
  189. }
  190. const commonApi = {
  191. cropImage,
  192. drawTrack,
  193. drawTracks,
  194. joinPath,
  195. downloadImage,
  196. imagesToPdf,
  197. logger,
  198. getConfigData,
  199. writeData,
  200. };
  201. export type CommonApi = typeof commonApi;
  202. export default commonApi;