A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

241 行
6.2KB

  1. import Utils from "/app/js/common/Utils.js";
  2. import GlobalEvents from "/app/js/common/EventCaller.js";
  3. import Input from "/app/js/ui/Input.js";
  4. import NESPalette from "/app/js/models/NESPalette.js";
  5. //import NESTile from "/app/js/models/NESTile.js";
  6. //import NESBank from "/app/js/models/NESBank.js";
  7. import ISurface from "/app/js/ifaces/ISurface.js";
  8. const EL_CANVAS_ID = "painter";
  9. /* --------------------------------------------------------------------
  10. * Univeral data and functions.
  11. ------------------------------------------------------------------- */
  12. var canvas = null;
  13. var context = null;
  14. function ResizeCanvasImg(w, h){
  15. if (canvas !== null){
  16. canvas.width = w;
  17. canvas.height = h;
  18. }
  19. };
  20. // Handling window resize events...
  21. var HANDLE_Resize = Utils.debounce(function(e){
  22. if (canvas !== null){
  23. ResizeCanvasImg(
  24. canvas.clientWidth,
  25. canvas.clientHeight
  26. );
  27. GlobalEvents.emit("resize", canvas.clientWidth, canvas.clientHeight);
  28. }
  29. }, 250);
  30. window.addEventListener("resize", HANDLE_Resize);
  31. // Setting-up Input controls.
  32. var input = new Input();
  33. input.enableKeyboardInput(true);
  34. input.enableMouseInput(true);
  35. input.preventDefaults = true;
  36. // Mouse handling...
  37. /*input.listen("mousemove", handle_mouseevent);
  38. input.listen("mousedown", handle_mouseevent);
  39. input.listen("mouseup", handle_mouseevent);
  40. input.listen("mouseclick", handle_mouseclickevent);
  41. */
  42. /* --------------------------------------------------------------------
  43. * CTRLPainter
  44. * Actual controlling class.
  45. ------------------------------------------------------------------- */
  46. // For reference...
  47. // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke
  48. class CTRLPainter {
  49. constructor(){
  50. this.__scale = 1.0; // This is the scale the painter will display source information.
  51. this.__offset = [0.0, 0.0]; // This is the X,Y offset from origin to display source information.
  52. this.__onePaletteMode = true; // If true, ALL tiles will be drawing using the same palette.
  53. this.__brushSize = 1;
  54. this.__brushColor = 0;
  55. this.__brushPalette = 0;
  56. this.__gridEnabled = false;
  57. this.__gridSize = 1;
  58. this.__surface = null;
  59. var self = this;
  60. var handle_resize = (function(w,h){
  61. this.render();
  62. }).bind(this);
  63. GlobalEvents.listen("resize", handle_resize);
  64. var handle_change_surface = (function(surf){
  65. if (!(surf instanceof ISurface)){
  66. console.log("WARNING: Attempted to set painter to non-surface instance.");
  67. return;
  68. }
  69. this.__surface = surf;
  70. this.center_surface();
  71. this.render();
  72. }).bind(this);
  73. GlobalEvents.listen("change_surface", handle_change_surface);
  74. var handle_color_change = (function(pi, ci){
  75. this.__brushPalette = pi;
  76. this.__brushColor = ci;
  77. }).bind(this);
  78. GlobalEvents.listen("active_palette_color", handle_color_change);
  79. var handle_offset = (function(e){
  80. this.__offset[0] += e.x - e.lastX;
  81. this.__offset[1] += e.y - e.lastY;
  82. this.render();
  83. }).bind(this);
  84. input.listen("shift+mouseleft+mousemove", handle_offset);
  85. var handle_scale = (function(e){
  86. if (e.delta < 0){
  87. this.scale_down();
  88. } else if (e.delta > 0){
  89. this.scale_up();
  90. }
  91. if (e.delta !== 0)
  92. this.render();
  93. }).bind(this);
  94. input.listen("wheel", handle_scale);
  95. }
  96. get onePaletteMode(){return this.__onePaletteMode;}
  97. set onePaletteMode(e){
  98. this.__onePaletteMode = (e === true);
  99. this.render();
  100. }
  101. get scale(){
  102. return this.__scale;
  103. }
  104. set scale(s){
  105. if (typeof(s) !== 'number')
  106. throw new TypeError("Expected number value.");
  107. this.__scale = Math.max(0.1, Math.min(100.0, s));
  108. }
  109. get showGrid(){return this.__gridEnabled;}
  110. set showGrid(e){
  111. this.__gridEnabled = (e === true);
  112. }
  113. initialize(){
  114. if (canvas === null){
  115. canvas = document.getElementById(EL_CANVAS_ID);
  116. if (!canvas)
  117. throw new Error("Failed to obtain the canvas element.");
  118. context = canvas.getContext("2d");
  119. if (!context)
  120. throw new Error("Failed to obtain canvas context.");
  121. context.imageSmoothingEnabled = false;
  122. ResizeCanvasImg(canvas.clientWidth, canvas.clientHeight); // A forced "resize".
  123. input.mouseTargetElement = canvas;
  124. this.center_surface();
  125. }
  126. return this;
  127. }
  128. scale_up(amount=1){
  129. this.scale = this.scale + (amount*0.1);
  130. return this;
  131. }
  132. scale_down(amount=1){
  133. this.scale = this.scale - (amount*0.1);
  134. return this;
  135. }
  136. center_surface(){
  137. if (canvas === null || this.__surface === null)
  138. return;
  139. this.__offset[0] = Math.floor((canvas.clientWidth - this.__surface.width) * 0.5);
  140. this.__offset[1] = Math.floor((canvas.clientHeight - this.__surface.height) * 0.5);
  141. return this;
  142. }
  143. render(){
  144. if (context === null || this.__surface === null)
  145. return;
  146. // Get the contexts initial fillStyle. Don't want the render operation to override it.
  147. var fillStyle = context.fillStyle;
  148. var scalemult = 1.0/this.__scale;
  149. // Clearing the context surface...
  150. context.fillStyle = NESPalette.Default[4];
  151. context.fillRect(
  152. 0,0,
  153. Math.floor(canvas.clientWidth),
  154. Math.floor(canvas.clientHeight)
  155. );
  156. for (var j = 0; j < this.__surface.height; j++){
  157. var y = Math.floor((j*scalemult) + this.__offset[1]);
  158. for (var i = 0; i < this.__surface.width; i++){
  159. var x = Math.floor((i*scalemult) + this.__offset[0]);
  160. if (x >= 0 && x < canvas.clientWidth && y >= 0 && y < canvas.clientHeight){
  161. var color = "#666666";
  162. if (this.__onePaletteMode){
  163. var pinfo = this.__surface.getColorIndex(i, j);
  164. if (pinfo.ci >= 0)
  165. color = NESPalette.Default[pinfo.ci];
  166. } else {
  167. color = this.__surface.getColor(i, j);
  168. }
  169. context.fillStyle = color;
  170. context.fillRect(
  171. x,y,
  172. 1, 1
  173. );
  174. }
  175. }
  176. }
  177. if (this.__gridEnabled && this.__scale > 0.5){
  178. // TODO: render the grid!
  179. }
  180. // Return to the context's initial fillStyle.
  181. context.fillStyle = fillStyle;
  182. return this;
  183. }
  184. }
  185. const instance = new CTRLPainter();
  186. export default instance;