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

258 行
7.0KB

  1. import Utils from "/app/js/common/Utils.js";
  2. import ISurface from "/app/js/ifaces/ISurface.js";
  3. import NESTile from "/app/js/models/NESTile.js";
  4. import NESPalette from "/app/js/models/NESPalette.js";
  5. function LRIdx2TileIdxCo(index, lid){
  6. if (isNaN(lid) || lid < 0 || lid > 2){
  7. lid = 2;
  8. }
  9. var res = {
  10. lid: 0,
  11. index: 0,
  12. x: 0,
  13. y: 0
  14. };
  15. var w = (lid == 2) ? 256 : 128;
  16. var x = Math.floor(index % w);
  17. var y = Math.floor(index / w);
  18. if (x < 128){
  19. res.index = (Math.floor(y/8) * 16) + Math.floor(x / 8);
  20. if (lid !== 2)
  21. res.lid = lid;
  22. } else {
  23. res.index = (Math.floor(y/8) * 16) + Math.floor((x - 128) / 8);
  24. res.lid = 1;
  25. }
  26. res.x = x % 8;
  27. res.y = y % 8;
  28. return res;
  29. }
  30. export default class NESBank extends ISurface{
  31. constructor(){
  32. super();
  33. this.__LP = []; // Left Patterns (Sprites)
  34. this.__RP = []; // Right Patterns (Backgrounds)
  35. this.__AccessMode = 2; // 0 = Sprites only | 1 = BG only | 2 = Sprites and BG
  36. for (var i=0; i < 256; i++){
  37. this.__LP.push(new NESTile());
  38. this.__RP.push(new NESTile());
  39. }
  40. this.__palette = null;
  41. }
  42. get access_mode(){return this.__AccessMode;}
  43. set access_mode(m){
  44. if (!Utils.isInt(m))
  45. throw new TypeError("Access mode expected to be integer.");
  46. switch(m){
  47. case NESBank.ACCESSMODE_SPRITE:
  48. this.__AccessMode = NESBank.ACCESSMODE_SPRITE; break;
  49. case NESBank.ACCESSMODE_BACKGROUND:
  50. this.__AccessMode = NESBank.ACCESSMODE_BACKGROUND; break;
  51. case NESBank.ACCESSMODE_FULL:
  52. this.__AccessMode = NESBank.ACCESSMODE_FULL; break;
  53. }
  54. }
  55. get json(){
  56. JSON.stringify({
  57. LP: this.__LP.map(x=>x.base64),
  58. RP: this.__RP.map(x=>x.base64)
  59. });
  60. }
  61. get chr(){
  62. var buff = new Uint8Array(8192);
  63. var offset = 0;
  64. this.__LP.forEach(function(i){
  65. buff.set(i.chr, offset);
  66. offset += 16;
  67. });
  68. this.__RP.forEach(function(i){
  69. buff.set(i.chr, offset);
  70. offset += 16;
  71. });
  72. return buff;
  73. }
  74. get palette(){return this.__palette;}
  75. set palette(p){
  76. if (p !== null && !(p instanceof NESPalette))
  77. throw new TypeError("Expected null or NESPalette object.");
  78. if (p !== this.__palette){
  79. this.__palette = p;
  80. }
  81. }
  82. get width(){return (this.__AccessMode == NESBank.ACCESSMODE_FULL) ? 256 : 128;}
  83. get height(){return 128;}
  84. get length(){return this.width * this.height;}
  85. get coloridx(){
  86. return new Proxy(this, {
  87. get:function(obj, prop){
  88. var len = obj.length * 8;
  89. if (prop === "length")
  90. return len;
  91. if (!Utils.isInt(prop))
  92. throw new TypeError("Expected integer index.");
  93. prop = parseInt(prop);
  94. if (prop < 0 || prop >= len)
  95. return NESPalette.Default[4];
  96. var res = LRIdx2TileIdxCo(prop, this.__AccessMode);
  97. var list = (res.lid === 0) ? obj.__LP : obj.__RP;
  98. return list[res.index].getPixelIndex(res.x, res.y);
  99. },
  100. set:function(obj, prop, value){
  101. if (!Utils.isInt(prop))
  102. throw new TypeError("Expected integer index.");
  103. if (!Utils.isInt(value))
  104. throw new TypeError("Color expected to be integer.");
  105. prop = parseInt(prop);
  106. value = parseInt(value);
  107. if (prop < 0 || prop >= len)
  108. throw new RangeError("Index out of bounds.");
  109. if (value < 0 || value >= 4)
  110. throw new RangeError("Color index out of bounds.");
  111. var res = LRIdx2TileIdxCo(prop, this.__AccessMode);
  112. var list = (res.lid === 0) ? obj.__LP : obj.__RP;
  113. list[res.index].setPixelIndex(res.x, res.y, value);
  114. return true;
  115. }
  116. });
  117. }
  118. get lp(){
  119. return new Proxy(this, {
  120. get: function(obj, prop){
  121. if (prop === "length")
  122. return obj.__LP.length;
  123. if (!Utils.isInt(prop))
  124. throw new TypeError("Expected integer index.");
  125. prop = parseInt(prop);
  126. if (prop < 0 || prop >= 256)
  127. throw new RangeError("Index out of bounds.");
  128. return obj.__LP[prop];
  129. },
  130. set: function(obj, prop, value){
  131. if (!Utils.isInt(prop))
  132. throw new TypeError("Expected integer index.");
  133. if (!(value instanceof NESTile))
  134. throw new TypeError("Can only assign NESTile objects.");
  135. prop = parseInt(prop);
  136. if (prop < 0 || prop >= 256)
  137. throw new RangeError("Index out of bounds.");
  138. obj.__LP[prop].copy(value);
  139. return true;
  140. }
  141. });
  142. }
  143. get rp(){
  144. return new Proxy(this, {
  145. get: function(obj, prop){
  146. if (prop === "length")
  147. return obj.__RP.length;
  148. if (!Utils.isInt(prop))
  149. throw new TypeError("Expected integer index.");
  150. prop = parseInt(prop);
  151. if (prop < 0 || prop >= 256)
  152. throw new RangeError("Index out of bounds.");
  153. return obj.__RP[prop];
  154. },
  155. set: function(obj, prop, value){
  156. if (!Utils.isInt(prop))
  157. throw new TypeError("Expected integer index.");
  158. if (!(value instanceof NESTile))
  159. throw new TypeError("Can only assign NESTile objects.");
  160. prop = parseInt(prop);
  161. if (prop < 0 || prop >= 256)
  162. throw new RangeError("Index out of bounds.");
  163. obj.__RP[prop].copy(value);
  164. return true;
  165. }
  166. });
  167. }
  168. copy(b){
  169. if (!(b instanceof NESBank))
  170. throw new TypeError("Expected NESBank object.");
  171. for (var i=0; i < 256; i++){
  172. this.lp[i] = b.lp[i];
  173. this.rp[i] = b.rp[i];
  174. }
  175. return this;
  176. }
  177. clone(){
  178. return (new NESBank()).copy(this);
  179. }
  180. getColor(x,y){
  181. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  182. return this.__default_pi[4];
  183. var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode);
  184. var list = (res.lid === 0) ? this.__LP : this.__RP;
  185. var pi = list[res.index].paletteIndex;
  186. var ci = list[res.index].getPixelIndex(res.x, res.y);
  187. if (this.__palette !== null){
  188. return this.__palette.get_palette_color(pi, ci);
  189. }
  190. return NESPalette.Default[ci];
  191. }
  192. getColorIndex(x, y){
  193. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  194. return {pi: -1, ci:-1};
  195. var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode);
  196. var list = (res.lid === 0) ? this.__LP : this.__RP;
  197. return {
  198. pi: list[res.index].paletteIndex,
  199. ci: list[res.index].getPixelIndex(res.x, res.y)
  200. };
  201. }
  202. setColorIndex(x, y, ci, pi){
  203. if (x < 0 || x >= this.width || y < 0 || y > this.height)
  204. throw new RangeError("Coordinates out of bounds.");
  205. if (!Utils.isInt(pi))
  206. pi = -1;
  207. if (!Utils.isInt(ci))
  208. ci = 0;
  209. if (pi < 0){
  210. this.coloridx[(y*this.width)+x] = ci;
  211. } else {
  212. var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode);
  213. var list = (res.lid === 0) ? this.__LP : this.__RP;
  214. list[res.index].paletteIndex = pi;
  215. list[res.index].setPixelIndex(res.x, res.y, ci);
  216. }
  217. return this;
  218. }
  219. }
  220. NESBank.ACCESSMODE_SPRITE = 0;
  221. NESBank.ACCESSMODE_BACKGROUND = 1;
  222. NESBank.ACCESSMODE_FULL = 2;