A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 line
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;