PixelPixie.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { Helper } from "../commons/helper";
  2. import { AbstractViewer } from "babylonjs-viewer";
  3. export class PixelPixieUtils {
  4. public static threshold = 3;
  5. public static errorRatio = 1;
  6. public static loadReferenceImage(name: string, callback: (success) => void) {
  7. const img = Helper.getReferenceImg();
  8. const timeout = setTimeout(() => {
  9. img.onload = null;
  10. if (callback) {
  11. callback(false);
  12. }
  13. }, 1000);
  14. img.onload = () => {
  15. clearTimeout(timeout);
  16. const resultCanvas = Helper.getReferenceCanvas();
  17. const resultContext = resultCanvas.getContext("2d");
  18. resultCanvas.width = img.width;
  19. resultCanvas.height = img.height;
  20. resultContext.drawImage(img, 0, 0);
  21. if (callback) {
  22. callback(true);
  23. }
  24. };
  25. img.src = "base/assets/referenceImages/" + name + ".png";
  26. }
  27. public static compare(canvas: HTMLCanvasElement, renderData: Uint8Array, stats: { maxDelta: number, differencesCount: number }): boolean {
  28. const referenceCanvas = canvas;
  29. const width = referenceCanvas.width;
  30. const height = referenceCanvas.height;
  31. const size = width * height * 4;
  32. const referenceContext = referenceCanvas.getContext("2d");
  33. const referenceData = referenceContext.getImageData(0, 0, width, height);
  34. let maxDelta = 0;
  35. let differencesCount = 0;
  36. for (let index = 0; index < size; index += 4) {
  37. const [r, g, b] = [index, index + 1, index + 2];
  38. const maxComponentDelta = Math.max(
  39. Math.abs(renderData[r] - referenceData.data[r]),
  40. Math.abs(renderData[g] - referenceData.data[g]),
  41. Math.abs(renderData[b] - referenceData.data[b])
  42. );
  43. maxDelta = Math.max(maxDelta, maxComponentDelta);
  44. if (maxComponentDelta < this.threshold) {
  45. continue;
  46. }
  47. referenceData.data[r] = 255;
  48. referenceData.data[g] *= 0.5;
  49. referenceData.data[b] *= 0.5;
  50. differencesCount++;
  51. }
  52. referenceContext.putImageData(referenceData, 0, 0);
  53. stats.maxDelta = maxDelta;
  54. stats.differencesCount = differencesCount;
  55. return (differencesCount * 100) / (width * height) > this.errorRatio;
  56. }
  57. public static getRenderData(renderEngine: AbstractViewer): Uint8Array {
  58. const width = renderEngine.canvas.width;
  59. const height = renderEngine.canvas.height;
  60. const renderData = renderEngine.engine.readPixels(0, 0, width, height);
  61. const numberOfChannelsByLine = width * 4;
  62. const halfHeight = height / 2;
  63. for (let i = 0; i < halfHeight; i++) {
  64. for (let j = 0; j < numberOfChannelsByLine; j++) {
  65. const currentCell = j + i * numberOfChannelsByLine;
  66. const targetLine = height - i - 1;
  67. const targetCell = j + targetLine * numberOfChannelsByLine;
  68. const temp = renderData[currentCell];
  69. renderData[currentCell] = renderData[targetCell];
  70. renderData[targetCell] = temp;
  71. }
  72. }
  73. return renderData;
  74. }
  75. public static evaluate(testName, renderEngine: AbstractViewer, callback: (result: boolean, picture: string, maxDelta: number, differencesCount: number) => void): void {
  76. renderEngine.scene.executeWhenReady(() => {
  77. // Extra render shouldn't be necessary, this is due to a bug in IE
  78. //TODO - this should be the viewer's render loop
  79. renderEngine.forceRender();
  80. const renderData = this.getRenderData(renderEngine);
  81. Helper.getRenderImg().src = renderEngine.canvas.toDataURL();
  82. renderEngine.runRenderLoop = false;
  83. this.loadReferenceImage(testName, (success) => {
  84. const referenceCanvas = Helper.getReferenceCanvas();
  85. if (success) {
  86. const stats = { maxDelta: 0, differencesCount: 0 };
  87. const different = this.compare(referenceCanvas, renderData, stats);
  88. const resultPicture = referenceCanvas.toDataURL();
  89. Helper.getReferenceImg().onload = null;
  90. Helper.getReferenceImg().src = resultPicture;
  91. callback(!different, resultPicture, stats.maxDelta, stats.differencesCount);
  92. }
  93. else {
  94. callback(false, "Reference image not loaded.", 0, 0);
  95. }
  96. });
  97. });
  98. }
  99. }