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

776 行
22KB

  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 CnvIdx(x, y, am, off){
  6. var res = {
  7. side: 0,
  8. tileidx: 0,
  9. x: 0,
  10. y: 0
  11. }
  12. switch(am){
  13. case NESBank.ACCESSMODE_8K:
  14. res.side = (x >= 128) ? 1 : 0;
  15. x -= (res.side === 1) ? 128 : 0;
  16. res.tileidx = (Math.floor(y/8) * 16) + Math.floor(x / 8);
  17. break;
  18. case NESBank.ACCESSMODE_4K:
  19. res.side = off;
  20. res.tileidx = (Math.floor(y/8) * 16) + Math.floor(x / 8);
  21. break;
  22. case NESBank.ACCESSMODE_2K:
  23. res.side = Math.floor(off * 0.5);
  24. off -= (off > 1) ? 2 : 0;
  25. res.tileidx = (off*128) + ((Math.floor(y/8) * 16) + Math.floor(x / 8));
  26. break;
  27. case NESBank.ACCESSMODE_1K:
  28. res.side = Math.floor(off * 0.25);
  29. off -= (off > 3) ? 4 : 0;
  30. res.tileidx = (off * 64) + ((Math.floor(y/8) * 16) + Math.floor(x / 8));
  31. break;
  32. case NESBank.ACCESSMODE_1T:
  33. res.side = (off >= 256) ? 1 : 0;
  34. res.tileidx = (off >= 256) ? off - 256 : off;
  35. break;
  36. case NESBank.ACCESSMODE_2T:
  37. res.side = (off >= 128) ? 1 : 0;
  38. off -= (off >= 128) ? 128 : 0;
  39. //res.tileidx = (Math.floor(off % 8)*32) + Math.floor(off / 16) + ((y >= 8) ? 16 : 0);
  40. res.tileidx = ((Math.floor(off/16)*32) + (off % 16)) + ((y >= 8) ? 16 : 0);
  41. }
  42. res.x = x%8;
  43. res.y = y%8;
  44. return res;
  45. }
  46. function AdjOffsetToNewMode(nmode, omode, ooff){
  47. // NOTE: 8K never shows up because it will ALWAYS return an offset of 0, so it's easier to just let it all fall through
  48. // to the default return value.
  49. switch(nmode){
  50. case NESBank.ACCESSMODE_4K:
  51. if (ooff > 1){
  52. switch(omode){
  53. case NESBank.ACCESSMODE_2K:
  54. return Math.floor(ooff * 0.5);
  55. case NESBank.ACCESSMODE_1K:
  56. return Math.floor(ooff * 0.25);
  57. case NESBank.ACCESSMODE_1T:
  58. return Math.floor(ooff / 256);
  59. case NESBank.ACCESSMODE_2T:
  60. return Math.floor(ooff / 128);
  61. }
  62. }
  63. return ooff;
  64. case NESBank.ACCESSMODE_2K:
  65. switch(omode){
  66. case NESBank.ACCESSMODE_4K:
  67. return ooff * 2;
  68. case NESBank.ACCESSMODE_1K:
  69. return Math.floor(ooff * 0.5);
  70. case NESBank.ACCESSMODE_1T:
  71. return Math.floor(ooff * 0.0078125); // divide by 128
  72. case NESBank.ACCESSMODE_2T:
  73. return Math.floor(ooff * 0.015625); // divide by 64
  74. }
  75. break;
  76. case NESBank.ACCESSMODE_1K:
  77. switch(omode){
  78. case NESBank.ACCESSMODE_4K:
  79. return ooff * 4;
  80. case NESBank.ACCESSMODE_2K:
  81. return ooff * 2;
  82. case NESBank.ACCESSMODE_1T:
  83. return Math.floor(ooff * 0.015625); // divide by 64
  84. case NESBank.ACCESSMODE_2T:
  85. return Math.floor(ooff * 0.03125); // divide by 32
  86. }
  87. break;
  88. case NESBank.ACCESSMODE_1T:
  89. switch(omode){
  90. case NESBank.ACCESSMODE_4K:
  91. return ooff * 256;
  92. case NESBank.ACCESSMODE_2K:
  93. return ooff * 128;
  94. case NESBank.ACCESSMODE_1K:
  95. return ooff * 64;
  96. case NESBank.ACCESSMODE_2T:
  97. return (Math.floor(ooff / 16)*32) + (ooff % 16);
  98. }
  99. break;
  100. case NESBank.ACCESSMODE_2T:
  101. switch(omode){
  102. case NESBank.ACCESSMODE_4K:
  103. return ooff * 128;
  104. case NESBank.ACCESSMODE_2K:
  105. return ooff * 64;
  106. case NESBank.ACCESSMODE_1K:
  107. return ooff * 32;
  108. case NESBank.ACCESSMODE_1T:
  109. let _off = (ooff >= 128) ? ooff - 128 : ooff;
  110. return ((ooff >= 128) ? 256 : 0) + ((Math.floor(_off / 16)*32) + (_off % 16));
  111. }
  112. break;
  113. }
  114. return 0;
  115. }
  116. export default class NESBank extends ISurface{
  117. constructor(){
  118. super();
  119. this.__LP = []; // Left Patterns (Sprites)
  120. this.__RP = []; // Right Patterns (Backgrounds)
  121. this.__View = [];
  122. this.__AccessMode = NESBank.ACCESSMODE_8K;
  123. this.__AccessOffset = 0;
  124. this.__undos = []; // Holds Base64 snapshots of the bank.
  125. this.__redos = []; // Holds Base64 snapshots of work undone.
  126. this.__emitsEnabled = true;
  127. var handle_datachanged = Utils.debounce((function(side, idx){
  128. var sendEmit = false;
  129. switch(this.__AccessMode){
  130. case NESBank.ACCESSMODE_2T:
  131. if (side === Math.floor(this.__AccessOffset / 128)){
  132. if (idx === this.__AccessOffset)
  133. sendEmit = true;
  134. }
  135. case NESBank.ACCESSMODE_1T:
  136. if (side === Math.floor(this.__AccessOffset / 256)){
  137. if (idx === this.__AccessOffset)
  138. sendEmit = true;
  139. }
  140. case NESBank.ACCESSMODE_1K:
  141. if (side === Math.floor(this.__AccessOffset / 4)){
  142. if (Math.floor(idx / 64) === Math.floor(this.__AccessOffset/4))
  143. sendEmit = true;
  144. }
  145. break;
  146. case NESBank.ACCESSMODE_2K:
  147. if (side === Math.floor(this.__AccessOffset / 2)){
  148. if (Math.floor(idx / 128) === Math.floor(this.__AccessOffset/2))
  149. sendEmit = true;
  150. }
  151. break;
  152. case NESBank.ACCESSMODE_4K:
  153. if (side === this.__AccessOffset)
  154. sendEmit = true;
  155. break;
  156. case NESBank.ACCESSMODE_8K:
  157. sendEmit = true;
  158. }
  159. if (sendEmit && this.__emitsEnabled){
  160. this.emit("data_changed");
  161. }
  162. }).bind(this), 250);
  163. for (var i=0; i < 256; i++){
  164. this.__LP.push(new NESTile());
  165. this.__LP[i].listen("data_changed", handle_datachanged.bind(this, 0, i));
  166. this.__RP.push(new NESTile());
  167. this.__RP[i].listen("data_changed", handle_datachanged.bind(this, 1, i));
  168. }
  169. this.__palette = null;
  170. }
  171. get access_mode(){return this.__AccessMode;}
  172. set access_mode(m){
  173. if (!Utils.isInt(m))
  174. throw new TypeError("Access mode expected to be integer.");
  175. var oam = this.__AccessMode;
  176. switch(m){
  177. case NESBank.ACCESSMODE_8K:
  178. this.__AccessMode = NESBank.ACCESSMODE_8K;
  179. break;
  180. case NESBank.ACCESSMODE_4K:
  181. this.__AccessMode = NESBank.ACCESSMODE_4K
  182. break;
  183. case NESBank.ACCESSMODE_2K:
  184. this.__AccessMode = NESBank.ACCESSMODE_2K;
  185. break;
  186. case NESBank.ACCESSMODE_1K:
  187. this.__AccessMode = NESBank.ACCESSMODE_1K;
  188. break;
  189. case NESBank.ACCESSMODE_1T:
  190. this.__AccessMode = NESBank.ACCESSMODE_1T;
  191. break;
  192. case NESBank.ACCESSMODE_2T:
  193. this.__AccessMode = NESBank.ACCESSMODE_2T;
  194. break;
  195. default:
  196. throw new Error("Unknown Access Mode.");
  197. }
  198. this.__AccessOffset = AdjOffsetToNewMode(m, oam, this.__AccessOffset);
  199. if (this.__emitsEnabled)
  200. this.emit("data_changed");
  201. }
  202. get access_offset(){return this.__AccessOffset;}
  203. set access_offset(o){
  204. if (!Utils.isInt(o))
  205. throw new TypeError("Access offset expected to be integer.");
  206. switch (this.__AccessMode){
  207. case NESBank.ACCESSMODE_8K:
  208. if (o !== 0)
  209. throw new RangeError("Access Offset is out of bounds based on current Access Mode.");
  210. break;
  211. case NESBank.ACCESSMODE_4K:
  212. if (o !== 0 && o !== 1)
  213. throw new RangeError("Access Offset is out of bounds based on current Access Mode.");
  214. break;
  215. case NESBank.ACCESSMODE_2K:
  216. if (o < 0 || o >= 4)
  217. throw new RangeError("Access Offset is out of bounds based on current Access Mode.");
  218. break;
  219. case NESBank.ACCESSMODE_1K:
  220. if (o < 0 || o >= 8)
  221. throw new RangeError("Access Offset is out of bounds based on current Access Mode.");
  222. break;
  223. case NESBank.ACCESSMODE_1T:
  224. if (o < 0 || o >= 512)
  225. throw new RangeError("Access Offset is out of bounds based on current Access Mode.");
  226. break;
  227. case NESBank.ACCESSMODE_2T:
  228. if (o < 0 || o >= 256)
  229. throw new RangeError("Access Offset is out of bounds based on current Access Mode.");
  230. break;
  231. }
  232. this.__AccessOffset = o;
  233. if (this.__emitsEnabled)
  234. this.emit("data_changed");
  235. }
  236. get access_offset_length(){
  237. switch(this.__AccessMode){
  238. case NESBank.ACCESSMODE_4K:
  239. return 2;
  240. case NESBank.ACCESSMODE_2K:
  241. return 4;
  242. case NESBank.ACCESSMODE_1K:
  243. return 8;
  244. case NESBank.ACCESSMODE_1T:
  245. return 512;
  246. case NESBank.ACCESSMODE_2T:
  247. return 256;
  248. }
  249. return 0;
  250. }
  251. get json(){
  252. JSON.stringify({
  253. LP: this.__LP.map(x=>x.base64),
  254. RP: this.__RP.map(x=>x.base64)
  255. });
  256. }
  257. get chr(){
  258. var buff = null;
  259. var offset = 0;
  260. switch (this.__AccessMode){
  261. case NESBank.ACCESSMODE_8K:
  262. buff = new Uint8Array(8192);
  263. this.__LP.forEach((i) => {
  264. buff.set(i.chr, offset);
  265. offset += 16;
  266. });
  267. this.__RP.forEach((i) => {
  268. buff.set(i.chr, offset);
  269. offset += 16;
  270. });
  271. break;
  272. case NESBank.ACCESSMODE_4K:
  273. buff = new Uint8Array(4096);
  274. var list = (this.__AccessOffset === 0) ? this.__LP : this.__RP;
  275. list.forEach((i) => {
  276. buff.set(i.chr, offset);
  277. offset += 16;
  278. });
  279. break;
  280. case NESBank.ACCESSMODE_2K:
  281. buff = new Uint8Array(2048);
  282. var list = (this.__AccessOffset < 2) ? this.__LP : this.__RP;
  283. var s = Math.floor(this.__AccessOffset * 0.5) * 128;
  284. var e = s + 128;
  285. for (let i=s; i < e; i++){
  286. buff.set(list[i].chr, offset);
  287. offset += 16;
  288. }
  289. break;
  290. case NESBank.ACCESSMODE_1K:
  291. buff = new Uint8Array(1024);
  292. var list = (this.__AccessOffset < 4) ? this.__LP : this.__RP;
  293. var s = Math.floor(this.__AccessOffset * 0.25) * 64;
  294. var e = s + 64;
  295. for (let i=s; i < e; i++){
  296. buff.set(list[i].chr, offset);
  297. offset += 16;
  298. }
  299. break;
  300. case NESBank.ACCESSMODE_1T:
  301. var list = (this.__AccessOffset < 256) ? this.__LP : this.__RP;
  302. var idx = this.__AccessOffset % 256;
  303. buff = list[idx].chr;
  304. break;
  305. case NESBank.ACCESSMODE_2T:
  306. var list = (this.__AccessOffset < 128) ? this.__LP : this.__RP;
  307. var off = this.__AccessOffset % 128;
  308. var idx = ((Math.floor(off / 16)*32) + (off % 16));
  309. buff = new Uint8Array(32);
  310. buff.set(list[idx].chr, 0);
  311. buff.set(list[idx+16].chr, 16);
  312. break;
  313. }
  314. return buff;
  315. }
  316. set chr(buff){
  317. if (!(buff instanceof Uint8Array))
  318. throw new TypeError("Expected Uint8Array buffer.");
  319. this.setCHR(buff);
  320. }
  321. get base64(){
  322. var b = "";
  323. var data = this.chr;
  324. for (var i = 0; i < data.length; i++) {
  325. b += String.fromCharCode(data[i]);
  326. }
  327. return window.btoa(b);
  328. }
  329. set base64(s){
  330. var b = window.atob(s);
  331. var len = b.length;
  332. if (b.length !== 8192){
  333. throw new Error("Base64 string contains invalid byte count.");
  334. }
  335. b = new Uint8Array(b.split("").map(function(c){
  336. return c.charCodeAt(0);
  337. }));
  338. this.chr = b;
  339. }
  340. get palette(){return this.__palette;}
  341. set palette(p){
  342. if (p !== null && !(p instanceof NESPalette))
  343. throw new TypeError("Expected null or NESPalette object.");
  344. if (p !== this.__palette){
  345. this.__palette = p;
  346. }
  347. }
  348. get width(){
  349. if (this.__AccessMode == NESBank.ACCESSMODE_8K)
  350. return 256;
  351. if (this.__AccessMode == NESBank.ACCESSMODE_1T || this.__AccessMode == NESBank.ACCESSMODE_2T)
  352. return 8;
  353. return 128;
  354. }
  355. get height(){
  356. switch(this.__AccessMode){
  357. case NESBank.ACCESSMODE_2K:
  358. return 64;
  359. case NESBank.ACCESSMODE_1K:
  360. return 32;
  361. case NESBank.ACCESSMODE_1T:
  362. return 8;
  363. case NESBank.ACCESSMODE_2T:
  364. return 16;
  365. }
  366. return 128;
  367. }
  368. get length(){return this.width * this.height;}
  369. get undos(){return this.__undos.length;}
  370. get redos(){return this.__redos.length;}
  371. get coloridx(){
  372. return new Proxy(this, {
  373. get:function(obj, prop){
  374. var len = obj.length * 8;
  375. if (prop === "length")
  376. return len;
  377. if (!Utils.isInt(prop))
  378. throw new TypeError("Expected integer index.");
  379. prop = parseInt(prop);
  380. if (prop < 0 || prop >= len)
  381. return NESPalette.Default[4];
  382. var x = Math.floor(prop % this.width);
  383. var y = Math.floor(prop / this.width);
  384. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  385. var list = (res.side === 0) ? obj.__LP : obj.__RP;
  386. return list[res.tileidx].getPixelIndex(res.x, res.y);
  387. },
  388. set:function(obj, prop, value){
  389. if (!Utils.isInt(prop))
  390. throw new TypeError("Expected integer index.");
  391. if (!Utils.isInt(value))
  392. throw new TypeError("Color expected to be integer.");
  393. prop = parseInt(prop);
  394. value = parseInt(value);
  395. if (prop < 0 || prop >= len)
  396. throw new RangeError("Index out of bounds.");
  397. if (value < 0 || value >= 4)
  398. throw new RangeError("Color index out of bounds.");
  399. var x = Math.floor(prop % this.width);
  400. var y = Math.floor(prop / this.width);
  401. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  402. var list = (res.side === 0) ? obj.__LP : obj.__RP;
  403. list[res.tileidx].setPixelIndex(res.x, res.y, value);
  404. return true;
  405. }
  406. });
  407. }
  408. get lp(){
  409. return new Proxy(this, {
  410. get: function(obj, prop){
  411. if (prop === "length")
  412. return obj.__LP.length;
  413. if (!Utils.isInt(prop))
  414. throw new TypeError("Expected integer index.");
  415. prop = parseInt(prop);
  416. if (prop < 0 || prop >= 256)
  417. throw new RangeError("Index out of bounds.");
  418. return obj.__LP[prop];
  419. },
  420. set: function(obj, prop, value){
  421. if (!Utils.isInt(prop))
  422. throw new TypeError("Expected integer index.");
  423. if (!(value instanceof NESTile))
  424. throw new TypeError("Can only assign NESTile objects.");
  425. prop = parseInt(prop);
  426. if (prop < 0 || prop >= 256)
  427. throw new RangeError("Index out of bounds.");
  428. obj.__LP[prop].copy(value);
  429. return true;
  430. }
  431. });
  432. }
  433. get rp(){
  434. return new Proxy(this, {
  435. get: function(obj, prop){
  436. if (prop === "length")
  437. return obj.__RP.length;
  438. if (!Utils.isInt(prop))
  439. throw new TypeError("Expected integer index.");
  440. prop = parseInt(prop);
  441. if (prop < 0 || prop >= 256)
  442. throw new RangeError("Index out of bounds.");
  443. return obj.__RP[prop];
  444. },
  445. set: function(obj, prop, value){
  446. if (!Utils.isInt(prop))
  447. throw new TypeError("Expected integer index.");
  448. if (!(value instanceof NESTile))
  449. throw new TypeError("Can only assign NESTile objects.");
  450. prop = parseInt(prop);
  451. if (prop < 0 || prop >= 256)
  452. throw new RangeError("Index out of bounds.");
  453. obj.__RP[prop].copy(value);
  454. return true;
  455. }
  456. });
  457. }
  458. copy(b){
  459. if (!(b instanceof NESBank))
  460. throw new TypeError("Expected NESBank object.");
  461. for (var i=0; i < 256; i++){
  462. this.lp[i] = b.lp[i];
  463. this.rp[i] = b.rp[i];
  464. }
  465. return this;
  466. }
  467. clone(){
  468. return (new NESBank()).copy(this);
  469. }
  470. getCHR(mode, offset){
  471. this.__emitsEnabled = false;
  472. var oam = this.access_mode;
  473. var oao = this.access_offset;
  474. try{
  475. this.access_mode = mode;
  476. this.access_offset = offset;
  477. } catch (e){
  478. this.access_mode = oam;
  479. this.access_offset = oao;
  480. this.__emitsEnabled = true;
  481. throw e;
  482. }
  483. var chr = this.chr;
  484. this.access_mode = oam;
  485. this.access_offset = oao;
  486. this.__emitsEnabled = true;
  487. return chr;
  488. }
  489. setCHR(buff, offset){
  490. if (!Utils.isInt(offset))
  491. offset = -1;
  492. var idx = 0;
  493. switch(buff.length){
  494. case 8192:
  495. if (offset < 0)
  496. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_8K, this.__AccessMode, this.__AccessOffset);
  497. this.__LP.forEach((i) => {
  498. i.chr = buff.slice(idx, idx+16);
  499. idx += 16;
  500. });
  501. this.__RP.forEach((i) => {
  502. i.chr = buff.slice(idx, idx+16);
  503. idx += 16;
  504. });
  505. break;
  506. case 4096:
  507. if (offset < 0)
  508. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_4K, this.__AccessMode, this.__AccessOffset);
  509. if (offset >= 2)
  510. throw new RangeError("Offset mismatch based on Buffer length.");
  511. var list = (offset === 0) ? this.__LP : this.__RP;
  512. list.forEach((i) => {
  513. i.chr = buff.slice(idx, idx+16);
  514. idx += 16;
  515. });
  516. break;
  517. case 2048:
  518. if (offset < 0)
  519. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_2K, this.__AccessMode, this.__AccessOffset);
  520. if (offset >= 4)
  521. throw new RangeError("Offset mismatch based on Buffer length.");
  522. var list = (offset < 2) ? this.__LP : this.__RP;
  523. var s = Math.floor(offset * 0.5) * 128;
  524. var e = s + 128;
  525. for (let i=s; i < e; i++){
  526. list[i].chr = buff.slice(idx, idx+16);
  527. idx += 16;
  528. }
  529. break;
  530. case 1024:
  531. if (offset < 0)
  532. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_1K, this.__AccessMode, this.__AccessOffset);
  533. if (offset >= 8)
  534. throw new RangeError("Offset mismatch based on Buffer length.");
  535. var list = (offset < 4) ? this.__LP : this.__RP;
  536. var s = Math.floor(this.__AccessOffset * 0.25) * 64;
  537. var e = s + 64;
  538. for (let i=s; i < e; i++){
  539. list[i].chr = buff.slice(idx, idx+16);
  540. idx += 16;
  541. }
  542. break;
  543. case 32:
  544. if (offset < 0)
  545. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_2T, this.__AccessMode, this.__AccessOffset);
  546. if (offset >= 256)
  547. throw new RangeError("Offset mismatch based on Buffer length.");
  548. var list = (offset < 128) ? this.__LP : this.__RP;
  549. var off = offset % 128;
  550. var idx = ((Math.floor(off / 32)*16) + (off % 16));
  551. list[idx].chr = buff.slice(0, 16);
  552. list[idx+16].char = buff.slice(16, 32);
  553. break;
  554. case 16:
  555. if (offset < 0)
  556. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_1T, this.__AccessMode, this.__AccessOffset);
  557. if (offset >= 512)
  558. throw new RangeError("Offset mismatch based on Buffer length.");
  559. var list = (offset < 256) ? this.__LP : this.__RP;
  560. var idx = offset % 256;
  561. list[idx].chr = buff;
  562. break;
  563. default:
  564. throw new RangeError("Buffer length does not match any of the supported bank sizes.");
  565. }
  566. return this;
  567. }
  568. getColor(x,y){
  569. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  570. return this.__default_pi[4];
  571. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  572. var list = (res.side === 0) ? this.__LP : this.__RP;
  573. var pi = list[res.tileidx].paletteIndex + ((res.side === 0) ? 4 : 0);
  574. var ci = list[res.tileidx].getPixelIndex(res.x, res.y);
  575. if (this.__palette !== null){
  576. return this.__palette.get_palette_color(pi, ci);
  577. }
  578. return NESPalette.Default[ci];
  579. }
  580. getColorIndex(x, y){
  581. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  582. return {pi: -1, ci:-1};
  583. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  584. var list = (res.side === 0) ? this.__LP : this.__RP;
  585. return {
  586. pi: list[res.tileidx].paletteIndex,
  587. ci: list[res.tileidx].getPixelIndex(res.x, res.y)
  588. };
  589. }
  590. getRegion(x, y, w, h){
  591. if (w <= 0 || h <= 0)
  592. throw new RangeError("Width and/or Height must be greater than zero.");
  593. var region = [];
  594. for (let j=y; j<y+h; j++){
  595. if (j === this.height){
  596. h = j - y;
  597. break;
  598. }
  599. for (let i=x; i<x+w; i++){
  600. if (i === this.width){
  601. w = i - x;
  602. break;
  603. }
  604. region.push(this.getColorIndex(i, j));
  605. }
  606. }
  607. return {w:w, h:h, r:region};
  608. }
  609. setColorIndex(x, y, ci, pi){
  610. if (x < 0 || x >= this.width || y < 0 || y > this.height)
  611. throw new RangeError("Coordinates out of bounds.");
  612. if (!Utils.isInt(pi))
  613. pi = -1;
  614. if (!Utils.isInt(ci))
  615. ci = 0;
  616. if (pi < 0){
  617. this.coloridx[(y*this.width)+x] = ci;
  618. } else {
  619. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  620. var list = (res.side === 0) ? this.__LP : this.__RP;
  621. list[res.tileidx].paletteIndex = pi;
  622. list[res.tileidx].setPixelIndex(res.x, res.y, ci);
  623. }
  624. return this;
  625. }
  626. setRegion(x, y, w, h, r){
  627. if (w <= 0 || h <= 0)
  628. throw new RangeError("Width and/or Height must be greater than zero.");
  629. if (!(r instanceof Array)){
  630. throw new TypeError("Region expected to be an array.");
  631. }
  632. if (r.length !== w*h)
  633. throw new RangeError("Region length does not match given width/height values.");
  634. for (let j=0; j < h; j++){
  635. for (let i=0; i < w; i++){
  636. var index = (j*w) + i;
  637. if ("pi" in r[index] && "ci" in r[index] && r[index].ci >= 0){
  638. var _x = i+x;
  639. var _y = j+y;
  640. if (_x >= 0 && _x < this.width && _y >= 0 && _y < this.height)
  641. this.setColorIndex(_x, _y, r[index].ci, r[index].pi);
  642. }
  643. }
  644. }
  645. return this;
  646. }
  647. snapshot(){
  648. if (this.__redos.length > 0) // Remove the redo history. We're adding a new snapshot.
  649. this.__redos = [];
  650. var snap = this.base64;
  651. if (this.__undos.length === this.__historyLength){
  652. this.__undos.pop();
  653. }
  654. this.__undos.splice(0,0,snap);
  655. return this;
  656. }
  657. undo(){
  658. if (this.__undos.length > 0){
  659. var usnap = this.__undos.splice(0, 1)[0];
  660. var rsnap = this.base64;
  661. this.base64 = usnap;
  662. if (this.__redos.length === this.__historyLength){
  663. this.__redos.pop();
  664. }
  665. this.__redos.splice(0,0,rsnap);
  666. }
  667. return this;
  668. }
  669. redo(){
  670. if (this.__redos.length > 0){
  671. var rsnap = this.__redos.splice(0,1)[0];
  672. var usnap = this.base64;
  673. this.base64 = rsnap;
  674. if (this.__undos.length === this.__historyLength){
  675. this.__undos.pop();
  676. }
  677. this.__undos.splice(0,0,usnap);
  678. }
  679. return this;
  680. }
  681. clearUndos(){
  682. this.__undos = [];
  683. return this;
  684. }
  685. clearRedos(){
  686. this.__redos = [];
  687. return this;
  688. }
  689. }
  690. NESBank.ACCESSMODE_8K = 0;
  691. NESBank.ACCESSMODE_1K = 1;
  692. NESBank.ACCESSMODE_2K = 2;
  693. NESBank.ACCESSMODE_4K = 3;
  694. NESBank.ACCESSMODE_1T = 4; // 8x8 Tile size
  695. NESBank.ACCESSMODE_2T = 5; // 8x16 Tile size