A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

312 Zeilen
7.9KB

  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. var ctximg = null;
  15. function OpenCanvasPixels(){
  16. if (context !== null){
  17. if (ctximg === null){
  18. ctximg = context.getImageData(0,0,Math.floor(canvas.clientWidth),Math.floor(canvas.clientHeight));
  19. }
  20. return (ctximg !== null)
  21. }
  22. return false;
  23. }
  24. function PutCanvasPixel(i,j,size,color){
  25. if (ctximg === null)
  26. return;
  27. i = Math.round(i);
  28. j = Math.round(j);
  29. size = Math.ceil(size);
  30. if (size <= 0){return;}
  31. var cw = Math.floor(canvas.clientWidth);
  32. var ch = Math.floor(canvas.clientHeight);
  33. var r = parseInt(color.substring(1, 3), 16);
  34. var g = parseInt(color.substring(3, 5), 16);
  35. var b = parseInt(color.substring(5, 7), 16);
  36. var idat = ctximg.data;
  37. for (var y=j; y < j+size; y++){
  38. for (var x=i; x < i+size; x++){
  39. if (x >= 0 && x < cw && y >= 0 && y < ch){
  40. var index = (y*cw*4) + (x*4);
  41. idat[index] = r;
  42. idat[index+1] = g;
  43. idat[index+2] = b;
  44. }
  45. }
  46. }
  47. }
  48. function CloseCanvasPixels(){
  49. if (ctximg !== null){
  50. context.putImageData(ctximg, 0, 0);
  51. ctximg = null;
  52. }
  53. }
  54. function ResizeCanvasImg(w, h){
  55. if (canvas !== null){
  56. canvas.width = w;
  57. canvas.height = h;
  58. }
  59. };
  60. // Handling window resize events...
  61. var HANDLE_Resize = Utils.debounce(function(e){
  62. if (canvas !== null){
  63. ResizeCanvasImg(
  64. canvas.clientWidth,
  65. canvas.clientHeight
  66. );
  67. GlobalEvents.emit("resize", canvas.clientWidth, canvas.clientHeight);
  68. }
  69. }, 250);
  70. window.addEventListener("resize", HANDLE_Resize);
  71. // Setting-up Input controls.
  72. var input = new Input();
  73. input.enableKeyboardInput(true);
  74. input.enableMouseInput(true);
  75. input.preventDefaults = true;
  76. // Mouse handling...
  77. /*input.listen("mousemove", handle_mouseevent);
  78. input.listen("mousedown", handle_mouseevent);
  79. input.listen("mouseup", handle_mouseevent);
  80. input.listen("mouseclick", handle_mouseclickevent);
  81. */
  82. /* --------------------------------------------------------------------
  83. * CTRLPainter
  84. * Actual controlling class.
  85. ------------------------------------------------------------------- */
  86. // For reference...
  87. // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke
  88. class CTRLPainter {
  89. constructor(){
  90. this.__scale = 1.0; // This is the scale the painter will display source information.
  91. this.__offset = [0.0, 0.0]; // This is the X,Y offset from origin to display source information.
  92. this.__onePaletteMode = true; // If true, ALL tiles will be drawing using the same palette.
  93. this.__brushSize = 1;
  94. this.__brushColor = 0;
  95. this.__brushPalette = 0;
  96. this.__gridEnabled = true; //false;
  97. this.__gridSize = 1;
  98. this.__surface = null;
  99. var self = this;
  100. var RenderD = Utils.throttle((function(){
  101. this.render();
  102. }).bind(this), 20);
  103. var handle_resize = (function(w,h){
  104. this.render();
  105. }).bind(this);
  106. GlobalEvents.listen("resize", handle_resize);
  107. var handle_change_surface = (function(surf){
  108. if (!(surf instanceof ISurface)){
  109. console.log("WARNING: Attempted to set painter to non-surface instance.");
  110. return;
  111. }
  112. this.__surface = surf;
  113. this.center_surface();
  114. RenderD();
  115. }).bind(this);
  116. GlobalEvents.listen("change_surface", handle_change_surface);
  117. var handle_color_change = (function(pi, ci){
  118. this.__brushPalette = pi;
  119. this.__brushColor = ci;
  120. }).bind(this);
  121. GlobalEvents.listen("active_palette_color", handle_color_change);
  122. var handle_offset = (function(e){
  123. this.__offset[0] += e.x - e.lastX;
  124. this.__offset[1] += e.y - e.lastY;
  125. RenderD();
  126. }).bind(this);
  127. input.listen("shift+mouseleft+mousemove", handle_offset);
  128. var handle_scale = (function(e){
  129. if (e.delta < 0){
  130. this.scale_down();
  131. } else if (e.delta > 0){
  132. this.scale_up();
  133. }
  134. if (e.delta !== 0)
  135. RenderD();
  136. }).bind(this);
  137. input.listen("wheel", handle_scale);
  138. }
  139. get onePaletteMode(){return this.__onePaletteMode;}
  140. set onePaletteMode(e){
  141. this.__onePaletteMode = (e === true);
  142. this.render();
  143. }
  144. get scale(){
  145. return this.__scale;
  146. }
  147. set scale(s){
  148. if (typeof(s) !== 'number')
  149. throw new TypeError("Expected number value.");
  150. this.__scale = Math.max(0.1, Math.min(100.0, s));
  151. }
  152. get showGrid(){return this.__gridEnabled;}
  153. set showGrid(e){
  154. this.__gridEnabled = (e === true);
  155. }
  156. initialize(){
  157. if (canvas === null){
  158. canvas = document.getElementById(EL_CANVAS_ID);
  159. if (!canvas)
  160. throw new Error("Failed to obtain the canvas element.");
  161. context = canvas.getContext("2d");
  162. if (!context)
  163. throw new Error("Failed to obtain canvas context.");
  164. context.imageSmoothingEnabled = false;
  165. ResizeCanvasImg(canvas.clientWidth, canvas.clientHeight); // A forced "resize".
  166. input.mouseTargetElement = canvas;
  167. this.center_surface();
  168. }
  169. return this;
  170. }
  171. scale_up(amount=1){
  172. this.scale = this.scale + (amount*0.1);
  173. return this;
  174. }
  175. scale_down(amount=1){
  176. this.scale = this.scale - (amount*0.1);
  177. return this;
  178. }
  179. center_surface(){
  180. if (canvas === null || this.__surface === null)
  181. return;
  182. this.__offset[0] = Math.floor((canvas.clientWidth - this.__surface.width) * 0.5);
  183. this.__offset[1] = Math.floor((canvas.clientHeight - this.__surface.height) * 0.5);
  184. return this;
  185. }
  186. render(){
  187. if (context === null || this.__surface === null)
  188. return;
  189. context.save();
  190. // Clearing the context surface...
  191. context.fillStyle = NESPalette.Default[4];
  192. context.fillRect(
  193. 0,0,
  194. Math.floor(canvas.clientWidth),
  195. Math.floor(canvas.clientHeight)
  196. );
  197. OpenCanvasPixels();
  198. for (var j = 0; j < this.__surface.height; j++){
  199. var y = (j*this.__scale) + this.__offset[1];
  200. for (var i = 0; i < this.__surface.width; i++){
  201. var x = (i*this.__scale) + this.__offset[0];
  202. if (x >= 0 && x < canvas.clientWidth && y >= 0 && y < canvas.clientHeight){
  203. var color = NESPalette.Default[4];
  204. if (this.__onePaletteMode){
  205. var pinfo = this.__surface.getColorIndex(i, j);
  206. if (pinfo.ci >= 0)
  207. color = NESPalette.Default[pinfo.ci];
  208. } else {
  209. color = this.__surface.getColor(i, j);
  210. }
  211. PutCanvasPixel(x,y, this.__scale, color);
  212. }
  213. }
  214. }
  215. CloseCanvasPixels();
  216. if (this.__gridEnabled && this.__scale > 0.5){
  217. context.strokeStyle = "#00FF00";
  218. var w = this.__surface.width * this.__scale;
  219. var h = this.__surface.height * this.__scale;
  220. var length = Math.max(this.__surface.width, this.__surface.height);
  221. for (var i=0; i < length; i += 8){
  222. var x = (i*this.__scale) + this.__offset[0];
  223. var y = (i*this.__scale) + this.__offset[1];
  224. if (i < this.__surface.width){
  225. context.beginPath();
  226. context.moveTo(x, this.__offset[1]);
  227. context.lineTo(x, this.__offset[1] + h);
  228. context.stroke();
  229. context.closePath();
  230. }
  231. if (i < this.__surface.height){
  232. context.beginPath();
  233. context.moveTo(this.__offset[0], y);
  234. context.lineTo(this.__offset[0] + w, y);
  235. context.stroke();
  236. context.closePath();
  237. }
  238. }
  239. }
  240. context.restore();
  241. return this;
  242. }
  243. }
  244. const instance = new CTRLPainter();
  245. export default instance;