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文字以内のものにしてください。

312 行
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;