A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

256 linhas
6.9KB

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