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

848 行
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