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.

848 satır
24KB

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