A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

258 líneas
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;