Source: Output/QRCanvas.js

  1. /**
  2. * @created 11.07.2022
  3. * @author smiley <smiley@chillerlan.net>
  4. * @copyright 2022 smiley
  5. * @license MIT
  6. */
  7. import QROutputAbstract from './QROutputAbstract.js';
  8. import QRCodeOutputException from './QRCodeOutputException.js';
  9. /**
  10. * @see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
  11. * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas
  12. */
  13. export default class QRCanvas extends QROutputAbstract{
  14. /**
  15. * @inheritDoc
  16. */
  17. mimeType = 'image/png';
  18. /**
  19. * @type {HTMLCanvasElement}
  20. * @protected
  21. */
  22. canvas;
  23. /**
  24. * @type {CanvasRenderingContext2D}
  25. * @protected
  26. */
  27. context;
  28. /**
  29. * @inheritDoc
  30. */
  31. static moduleValueIsValid($value){
  32. if(typeof $value !== 'string'){
  33. return false;
  34. }
  35. $value = $value.trim();
  36. // hex notation
  37. // #rgb(a)
  38. // #rrggbb(aa)
  39. if($value.match(/^#([\da-f]{3}){1,2}$|^#([\da-f]{4}){1,2}$/i)){
  40. return true;
  41. }
  42. // css: hsla/rgba(...values)
  43. if($value.match(/^(hsla?|rgba?)\([\d .,%\/]+\)$/i)){
  44. return true;
  45. }
  46. // predefined css color
  47. if($value.match(/^[a-z]+$/i)){
  48. return true;
  49. }
  50. return false;
  51. }
  52. /**
  53. * @inheritDoc
  54. */
  55. prepareModuleValue($value){
  56. return $value.trim();
  57. }
  58. /**
  59. * @inheritDoc
  60. */
  61. getDefaultModuleValue($isDark){
  62. return $isDark ? '#000' : '#fff';
  63. }
  64. /**
  65. * @inheritDoc
  66. */
  67. getOutputDimensions(){
  68. return [this.length, this.length];
  69. }
  70. /**
  71. * @param {string} $data (ignored)
  72. * @param {string} $mime (ignored)
  73. * @returns {string}
  74. * @protected
  75. */
  76. toBase64DataURI($data, $mime){
  77. $mime = this.options.canvasMimeType.trim();
  78. if($mime === ''){
  79. $mime = this.mimeType;
  80. }
  81. return this.canvas.toDataURL($mime, this.options.canvasImageQuality)
  82. }
  83. /**
  84. * @inheritDoc
  85. *
  86. * @returns {HTMLCanvasElement|string|*}
  87. * @throws {QRCodeOutputException}
  88. */
  89. dump($file = null){
  90. this.canvas = (this.options.canvasElement || document.createElement('canvas'));
  91. // @todo: test if instance check also works with nodejs canvas modules etc.
  92. if(!this.canvas || !(this.canvas instanceof HTMLCanvasElement) || (typeof this.canvas.getContext !== 'function')){
  93. throw new QRCodeOutputException('invalid canvas element');
  94. }
  95. this.drawImage();
  96. if(this.options.returnAsDomElement){
  97. return this.canvas;
  98. }
  99. let base64DataURI = this.toBase64DataURI();
  100. let rawImage = atob(base64DataURI.split(',')[1]);
  101. this.saveToFile(rawImage, $file);
  102. if(this.options.outputBase64){
  103. return base64DataURI;
  104. }
  105. return rawImage;
  106. }
  107. /**
  108. * @returns {void}
  109. * @protected
  110. */
  111. drawImage(){
  112. this.canvas.width = this.length;
  113. this.canvas.height = this.length;
  114. this.context = this.canvas.getContext('2d', {alpha: this.options.imageTransparent})
  115. if(this.options.bgcolor && this.constructor.moduleValueIsValid(this.options.bgcolor)){
  116. this.context.fillStyle = this.options.bgcolor;
  117. this.context.fillRect(0, 0, this.length, this.length);
  118. }
  119. for(let $y = 0; $y < this.moduleCount; $y++){
  120. for(let $x = 0; $x < this.moduleCount; $x++){
  121. this.module($x, $y, this.matrix.get($x, $y))
  122. }
  123. }
  124. }
  125. /**
  126. * @returns {void}
  127. * @protected
  128. */
  129. module($x, $y, $M_TYPE){
  130. if(!this.options.drawLightModules && !this.matrix.check($x, $y)){
  131. return;
  132. }
  133. this.context.fillStyle = this.getModuleValue($M_TYPE);
  134. if(this.options.drawCircularModules && !this.matrix.checkTypeIn($x, $y, this.options.keepAsSquare)){
  135. this.context.beginPath();
  136. this.context.arc(
  137. ($x + 0.5) * this.scale,
  138. ($y + 0.5) * this.scale,
  139. (this.options.circleRadius * this.scale),
  140. 0,
  141. 2 * Math.PI
  142. )
  143. this.context.fill();
  144. return;
  145. }
  146. this.context.fillRect($x * this.scale, $y * this.scale, this.scale, this.scale);
  147. }
  148. }