HTMLCanvasElementLuminanceSource.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import InvertedLuminanceSource from '../core/InvertedLuminanceSource';
  2. import LuminanceSource from '../core/LuminanceSource';
  3. import IllegalArgumentException from '../core/IllegalArgumentException';
  4. /**
  5. * @deprecated Moving to @zxing/browser
  6. */
  7. export class HTMLCanvasElementLuminanceSource extends LuminanceSource {
  8. constructor(canvas) {
  9. super(canvas.width, canvas.height);
  10. this.canvas = canvas;
  11. this.tempCanvasElement = null;
  12. this.buffer = HTMLCanvasElementLuminanceSource.makeBufferFromCanvasImageData(canvas);
  13. }
  14. static makeBufferFromCanvasImageData(canvas) {
  15. const imageData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
  16. return HTMLCanvasElementLuminanceSource.toGrayscaleBuffer(imageData.data, canvas.width, canvas.height);
  17. }
  18. static toGrayscaleBuffer(imageBuffer, width, height) {
  19. const grayscaleBuffer = new Uint8ClampedArray(width * height);
  20. HTMLCanvasElementLuminanceSource.FRAME_INDEX = !HTMLCanvasElementLuminanceSource.FRAME_INDEX;
  21. if (HTMLCanvasElementLuminanceSource.FRAME_INDEX) {
  22. for (let i = 0, j = 0, length = imageBuffer.length; i < length; i += 4, j++) {
  23. let gray;
  24. const alpha = imageBuffer[i + 3];
  25. // The color of fully-transparent pixels is irrelevant. They are often, technically, fully-transparent
  26. // black (0 alpha, and then 0 RGB). They are often used, of course as the "white" area in a
  27. // barcode image. Force any such pixel to be white:
  28. if (alpha === 0) {
  29. gray = 0xFF;
  30. }
  31. else {
  32. const pixelR = imageBuffer[i];
  33. const pixelG = imageBuffer[i + 1];
  34. const pixelB = imageBuffer[i + 2];
  35. // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC),
  36. // (306*R) >> 10 is approximately equal to R*0.299, and so on.
  37. // 0x200 >> 10 is 0.5, it implements rounding.
  38. gray = (306 * pixelR +
  39. 601 * pixelG +
  40. 117 * pixelB +
  41. 0x200) >> 10;
  42. }
  43. grayscaleBuffer[j] = gray;
  44. }
  45. }
  46. else {
  47. for (let i = 0, j = 0, length = imageBuffer.length; i < length; i += 4, j++) {
  48. let gray;
  49. const alpha = imageBuffer[i + 3];
  50. // The color of fully-transparent pixels is irrelevant. They are often, technically, fully-transparent
  51. // black (0 alpha, and then 0 RGB). They are often used, of course as the "white" area in a
  52. // barcode image. Force any such pixel to be white:
  53. if (alpha === 0) {
  54. gray = 0xFF;
  55. }
  56. else {
  57. const pixelR = imageBuffer[i];
  58. const pixelG = imageBuffer[i + 1];
  59. const pixelB = imageBuffer[i + 2];
  60. // .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC),
  61. // (306*R) >> 10 is approximately equal to R*0.299, and so on.
  62. // 0x200 >> 10 is 0.5, it implements rounding.
  63. gray = (306 * pixelR +
  64. 601 * pixelG +
  65. 117 * pixelB +
  66. 0x200) >> 10;
  67. }
  68. grayscaleBuffer[j] = 0xFF - gray;
  69. }
  70. }
  71. return grayscaleBuffer;
  72. }
  73. getRow(y /*int*/, row) {
  74. if (y < 0 || y >= this.getHeight()) {
  75. throw new IllegalArgumentException('Requested row is outside the image: ' + y);
  76. }
  77. const width = this.getWidth();
  78. const start = y * width;
  79. if (row === null) {
  80. row = this.buffer.slice(start, start + width);
  81. }
  82. else {
  83. if (row.length < width) {
  84. row = new Uint8ClampedArray(width);
  85. }
  86. // The underlying raster of image consists of bytes with the luminance values
  87. // TODO: can avoid set/slice?
  88. row.set(this.buffer.slice(start, start + width));
  89. }
  90. return row;
  91. }
  92. getMatrix() {
  93. return this.buffer;
  94. }
  95. isCropSupported() {
  96. return true;
  97. }
  98. crop(left /*int*/, top /*int*/, width /*int*/, height /*int*/) {
  99. super.crop(left, top, width, height);
  100. return this;
  101. }
  102. /**
  103. * This is always true, since the image is a gray-scale image.
  104. *
  105. * @return true
  106. */
  107. isRotateSupported() {
  108. return true;
  109. }
  110. rotateCounterClockwise() {
  111. this.rotate(-90);
  112. return this;
  113. }
  114. rotateCounterClockwise45() {
  115. this.rotate(-45);
  116. return this;
  117. }
  118. getTempCanvasElement() {
  119. if (null === this.tempCanvasElement) {
  120. const tempCanvasElement = this.canvas.ownerDocument.createElement('canvas');
  121. tempCanvasElement.width = this.canvas.width;
  122. tempCanvasElement.height = this.canvas.height;
  123. this.tempCanvasElement = tempCanvasElement;
  124. }
  125. return this.tempCanvasElement;
  126. }
  127. rotate(angle) {
  128. const tempCanvasElement = this.getTempCanvasElement();
  129. const tempContext = tempCanvasElement.getContext('2d');
  130. const angleRadians = angle * HTMLCanvasElementLuminanceSource.DEGREE_TO_RADIANS;
  131. // Calculate and set new dimensions for temp canvas
  132. const width = this.canvas.width;
  133. const height = this.canvas.height;
  134. const newWidth = Math.ceil(Math.abs(Math.cos(angleRadians)) * width + Math.abs(Math.sin(angleRadians)) * height);
  135. const newHeight = Math.ceil(Math.abs(Math.sin(angleRadians)) * width + Math.abs(Math.cos(angleRadians)) * height);
  136. tempCanvasElement.width = newWidth;
  137. tempCanvasElement.height = newHeight;
  138. // Draw at center of temp canvas to prevent clipping of image data
  139. tempContext.translate(newWidth / 2, newHeight / 2);
  140. tempContext.rotate(angleRadians);
  141. tempContext.drawImage(this.canvas, width / -2, height / -2);
  142. this.buffer = HTMLCanvasElementLuminanceSource.makeBufferFromCanvasImageData(tempCanvasElement);
  143. return this;
  144. }
  145. invert() {
  146. return new InvertedLuminanceSource(this);
  147. }
  148. }
  149. HTMLCanvasElementLuminanceSource.DEGREE_TO_RADIANS = Math.PI / 180;
  150. HTMLCanvasElementLuminanceSource.FRAME_INDEX = true;