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个字符

830 行
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. var b = "";
  324. var data = this.chr;
  325. for (var i = 0; i < data.length; i++) {
  326. b += String.fromCharCode(data[i]);
  327. }
  328. return window.btoa(b);
  329. }
  330. set base64(s){
  331. var b = window.atob(s);
  332. var len = b.length;
  333. if (b.length !== 8192){
  334. throw new Error("Base64 string contains invalid byte count.");
  335. }
  336. b = new Uint8Array(b.split("").map(function(c){
  337. return c.charCodeAt(0);
  338. }));
  339. this.chr = b;
  340. }
  341. get palette(){return this.__palette;}
  342. set palette(p){
  343. if (p !== null && !(p instanceof NESPalette))
  344. throw new TypeError("Expected null or NESPalette object.");
  345. if (p !== this.__palette){
  346. this.__palette = p;
  347. }
  348. }
  349. get width(){
  350. if (this.__AccessMode == NESBank.ACCESSMODE_8K)
  351. return 256;
  352. if (this.__AccessMode == NESBank.ACCESSMODE_1T || this.__AccessMode == NESBank.ACCESSMODE_2T)
  353. return 8;
  354. return 128;
  355. }
  356. get height(){
  357. switch(this.__AccessMode){
  358. case NESBank.ACCESSMODE_2K:
  359. return 64;
  360. case NESBank.ACCESSMODE_1K:
  361. return 32;
  362. case NESBank.ACCESSMODE_1T:
  363. return 8;
  364. case NESBank.ACCESSMODE_2T:
  365. return 16;
  366. }
  367. return 128;
  368. }
  369. get length(){return this.width * this.height;}
  370. get undos(){return this.__undos.length;}
  371. get redos(){return this.__redos.length;}
  372. get coloridx(){
  373. return new Proxy(this, {
  374. get:function(obj, prop){
  375. var len = obj.length * 8;
  376. if (prop === "length")
  377. return len;
  378. if (!Utils.isInt(prop))
  379. throw new TypeError("Expected integer index.");
  380. prop = parseInt(prop);
  381. if (prop < 0 || prop >= len)
  382. return NESPalette.Default[4];
  383. var x = Math.floor(prop % this.width);
  384. var y = Math.floor(prop / this.width);
  385. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  386. var list = (res.side === 0) ? obj.__LP : obj.__RP;
  387. return list[res.tileidx].getPixelIndex(res.x, res.y);
  388. },
  389. set:function(obj, prop, value){
  390. if (!Utils.isInt(prop))
  391. throw new TypeError("Expected integer index.");
  392. if (!Utils.isInt(value))
  393. throw new TypeError("Color expected to be integer.");
  394. prop = parseInt(prop);
  395. value = parseInt(value);
  396. if (prop < 0 || prop >= len)
  397. throw new RangeError("Index out of bounds.");
  398. if (value < 0 || value >= 4)
  399. throw new RangeError("Color index out of bounds.");
  400. var x = Math.floor(prop % this.width);
  401. var y = Math.floor(prop / this.width);
  402. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  403. var list = (res.side === 0) ? obj.__LP : obj.__RP;
  404. list[res.tileidx].setPixelIndex(res.x, res.y, value);
  405. return true;
  406. }
  407. });
  408. }
  409. get lp(){
  410. return new Proxy(this, {
  411. get: function(obj, prop){
  412. if (prop === "length")
  413. return obj.__LP.length;
  414. if (!Utils.isInt(prop))
  415. throw new TypeError("Expected integer index.");
  416. prop = parseInt(prop);
  417. if (prop < 0 || prop >= 256)
  418. throw new RangeError("Index out of bounds.");
  419. return obj.__LP[prop];
  420. },
  421. set: function(obj, prop, value){
  422. if (!Utils.isInt(prop))
  423. throw new TypeError("Expected integer index.");
  424. if (!(value instanceof NESTile))
  425. throw new TypeError("Can only assign NESTile objects.");
  426. prop = parseInt(prop);
  427. if (prop < 0 || prop >= 256)
  428. throw new RangeError("Index out of bounds.");
  429. obj.__LP[prop].copy(value);
  430. return true;
  431. }
  432. });
  433. }
  434. get rp(){
  435. return new Proxy(this, {
  436. get: function(obj, prop){
  437. if (prop === "length")
  438. return obj.__RP.length;
  439. if (!Utils.isInt(prop))
  440. throw new TypeError("Expected integer index.");
  441. prop = parseInt(prop);
  442. if (prop < 0 || prop >= 256)
  443. throw new RangeError("Index out of bounds.");
  444. return obj.__RP[prop];
  445. },
  446. set: function(obj, prop, value){
  447. if (!Utils.isInt(prop))
  448. throw new TypeError("Expected integer index.");
  449. if (!(value instanceof NESTile))
  450. throw new TypeError("Can only assign NESTile objects.");
  451. prop = parseInt(prop);
  452. if (prop < 0 || prop >= 256)
  453. throw new RangeError("Index out of bounds.");
  454. obj.__RP[prop].copy(value);
  455. return true;
  456. }
  457. });
  458. }
  459. copy(b){
  460. if (!(b instanceof NESBank))
  461. throw new TypeError("Expected NESBank object.");
  462. for (var i=0; i < 256; i++){
  463. this.lp[i] = b.lp[i];
  464. this.rp[i] = b.rp[i];
  465. }
  466. return this;
  467. }
  468. clone(){
  469. return (new NESBank()).copy(this);
  470. }
  471. getCHR(mode, offset){
  472. this.__emitsEnabled = false;
  473. var oam = this.access_mode;
  474. var oao = this.access_offset;
  475. try{
  476. this.access_mode = mode;
  477. this.access_offset = offset;
  478. } catch (e){
  479. this.access_mode = oam;
  480. this.access_offset = oao;
  481. this.__emitsEnabled = true;
  482. throw e;
  483. }
  484. var chr = this.chr;
  485. this.access_mode = oam;
  486. this.access_offset = oao;
  487. this.__emitsEnabled = true;
  488. return chr;
  489. }
  490. setCHR(buff, offset){
  491. if (!Utils.isInt(offset))
  492. offset = -1;
  493. var idx = 0;
  494. switch(buff.length){
  495. case 8192:
  496. if (offset < 0)
  497. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_8K, this.__AccessMode, this.__AccessOffset);
  498. this.__LP.forEach((i) => {
  499. i.chr = buff.slice(idx, idx+16);
  500. idx += 16;
  501. });
  502. this.__RP.forEach((i) => {
  503. i.chr = buff.slice(idx, idx+16);
  504. idx += 16;
  505. });
  506. break;
  507. case 4096:
  508. if (offset < 0)
  509. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_4K, this.__AccessMode, this.__AccessOffset);
  510. if (offset >= 2)
  511. throw new RangeError("Offset mismatch based on Buffer length.");
  512. var list = (offset === 0) ? this.__LP : this.__RP;
  513. list.forEach((i) => {
  514. i.chr = buff.slice(idx, idx+16);
  515. idx += 16;
  516. });
  517. break;
  518. case 2048:
  519. if (offset < 0)
  520. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_2K, this.__AccessMode, this.__AccessOffset);
  521. if (offset >= 4)
  522. throw new RangeError("Offset mismatch based on Buffer length.");
  523. var list = (offset < 2) ? this.__LP : this.__RP;
  524. var s = Math.floor(offset * 0.5) * 128;
  525. var e = s + 128;
  526. for (let i=s; i < e; i++){
  527. list[i].chr = buff.slice(idx, idx+16);
  528. idx += 16;
  529. }
  530. break;
  531. case 1024:
  532. if (offset < 0)
  533. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_1K, this.__AccessMode, this.__AccessOffset);
  534. if (offset >= 8)
  535. throw new RangeError("Offset mismatch based on Buffer length.");
  536. var list = (offset < 4) ? this.__LP : this.__RP;
  537. var s = Math.floor(this.__AccessOffset * 0.25) * 64;
  538. var e = s + 64;
  539. for (let i=s; i < e; i++){
  540. list[i].chr = buff.slice(idx, idx+16);
  541. idx += 16;
  542. }
  543. break;
  544. case 32:
  545. if (offset < 0)
  546. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_2T, this.__AccessMode, this.__AccessOffset);
  547. if (offset >= 256)
  548. throw new RangeError("Offset mismatch based on Buffer length.");
  549. var list = (offset < 128) ? this.__LP : this.__RP;
  550. var off = offset % 128;
  551. var idx = ((Math.floor(off / 32)*16) + (off % 16));
  552. list[idx].chr = buff.slice(0, 16);
  553. list[idx+16].char = buff.slice(16, 32);
  554. break;
  555. case 16:
  556. if (offset < 0)
  557. offset = AdjOffsetToNewMode(NESBank.ACCESSMODE_1T, this.__AccessMode, this.__AccessOffset);
  558. if (offset >= 512)
  559. throw new RangeError("Offset mismatch based on Buffer length.");
  560. var list = (offset < 256) ? this.__LP : this.__RP;
  561. var idx = offset % 256;
  562. list[idx].chr = buff;
  563. break;
  564. default:
  565. throw new RangeError("Buffer length does not match any of the supported bank sizes.");
  566. }
  567. return this;
  568. }
  569. getColor(x,y){
  570. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  571. return this.__default_pi[4];
  572. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  573. var list = (res.side === 0) ? this.__LP : this.__RP;
  574. var pi = list[res.tileidx].paletteIndex + ((res.side === 0) ? 4 : 0);
  575. var ci = list[res.tileidx].getPixelIndex(res.x, res.y);
  576. if (this.__palette !== null){
  577. return this.__palette.get_palette_color(pi, ci);
  578. }
  579. return NESPalette.Default[ci];
  580. }
  581. getColorIndex(x, y){
  582. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  583. return {pi: -1, ci:-1};
  584. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  585. var list = (res.side === 0) ? this.__LP : this.__RP;
  586. return {
  587. pi: list[res.tileidx].paletteIndex,
  588. ci: list[res.tileidx].getPixelIndex(res.x, res.y)
  589. };
  590. }
  591. getRegion(x, y, w, h){
  592. if (w <= 0 || h <= 0)
  593. throw new RangeError("Width and/or Height must be greater than zero.");
  594. var region = [];
  595. for (let j=y; j<y+h; j++){
  596. if (j === this.height){
  597. h = j - y;
  598. break;
  599. }
  600. for (let i=x; i<x+w; i++){
  601. if (i === this.width){
  602. w = i - x;
  603. break;
  604. }
  605. region.push(this.getColorIndex(i, j));
  606. }
  607. }
  608. return {w:w, h:h, r:region};
  609. }
  610. setColorIndex(x, y, ci, pi){
  611. if (x < 0 || x >= this.width || y < 0 || y > this.height)
  612. throw new RangeError("Coordinates out of bounds.");
  613. if (!Utils.isInt(pi))
  614. pi = -1;
  615. if (!Utils.isInt(ci))
  616. ci = 0;
  617. if (pi < 0){
  618. this.coloridx[(y*this.width)+x] = ci;
  619. } else {
  620. var res = CnvIdx(x, y, this.__AccessMode, this.__AccessOffset);
  621. var list = (res.side === 0) ? this.__LP : this.__RP;
  622. list[res.tileidx].paletteIndex = pi;
  623. list[res.tileidx].setPixelIndex(res.x, res.y, ci);
  624. }
  625. return this;
  626. }
  627. setRegion(x, y, w, h, r){
  628. if (w <= 0 || h <= 0)
  629. throw new RangeError("Width and/or Height must be greater than zero.");
  630. if (!(r instanceof Array)){
  631. throw new TypeError("Region expected to be an array.");
  632. }
  633. if (r.length !== w*h)
  634. throw new RangeError("Region length does not match given width/height values.");
  635. for (let j=0; j < h; j++){
  636. for (let i=0; i < w; i++){
  637. var index = (j*w) + i;
  638. if ("pi" in r[index] && "ci" in r[index] && r[index].ci >= 0){
  639. var _x = i+x;
  640. var _y = j+y;
  641. if (_x >= 0 && _x < this.width && _y >= 0 && _y < this.height)
  642. this.setColorIndex(_x, _y, r[index].ci, r[index].pi);
  643. }
  644. }
  645. }
  646. return this;
  647. }
  648. snapshot(){
  649. if (this.__redos.length > 0) // Remove the redo history. We're adding a new snapshot.
  650. this.__redos = [];
  651. var snap = this.base64;
  652. if (this.__undos.length === this.__historyLength){
  653. this.__undos.pop();
  654. }
  655. this.__undos.splice(0,0,snap);
  656. return this;
  657. }
  658. undo(){
  659. if (this.__undos.length > 0){
  660. var usnap = this.__undos.splice(0, 1)[0];
  661. var rsnap = this.base64;
  662. this.base64 = usnap;
  663. if (this.__redos.length === this.__historyLength){
  664. this.__redos.pop();
  665. }
  666. this.__redos.splice(0,0,rsnap);
  667. }
  668. return this;
  669. }
  670. redo(){
  671. if (this.__redos.length > 0){
  672. var rsnap = this.__redos.splice(0,1)[0];
  673. var usnap = this.base64;
  674. this.base64 = rsnap;
  675. if (this.__undos.length === this.__historyLength){
  676. this.__undos.pop();
  677. }
  678. this.__undos.splice(0,0,usnap);
  679. }
  680. return this;
  681. }
  682. clearUndos(){
  683. this.__undos = [];
  684. return this;
  685. }
  686. clearRedos(){
  687. this.__redos = [];
  688. return this;
  689. }
  690. removeDuplicates(sameOrientation){
  691. sameOrientation = (sameOrientation === true);
  692. var remdup = (list, idxoff) => {
  693. for (let i=1; i < list.length; i++){
  694. for (let j=0; j < i; j++){
  695. var res = list[i].isEq(list[j], sameOrientation);
  696. if (res >= 0){
  697. list[i].clear();
  698. this.emit("tile_drop", {
  699. oIdx: idxoff + i,
  700. nIdx: idxoff + j,
  701. flag: res
  702. });
  703. }
  704. }
  705. }
  706. };
  707. this.clearUndos();
  708. this.clearRedos();
  709. remdup(this.__LP, 0);
  710. remdup(this.__RP, 256);
  711. return this;
  712. }
  713. compact(ignoreTileZero){
  714. ignoreTileZero = (ignoreTileZero === true);
  715. var comp = (list, idxoff, it0) => {
  716. var etile = (it0) ? 1 : 0;
  717. while (etile < list.length && !list[etile].isEmpty()){
  718. etile++;
  719. }
  720. var ctile = etile + 1;
  721. for (ctile; ctile < list.length; ctile++){
  722. if (!list[ctile].isEmpty()){
  723. list[etile].copy(list[ctile]);
  724. list[ctile].clear();
  725. this.emit("tile_move", {
  726. oIdx: idxoff + ctile,
  727. nIdx: idxoff + etile
  728. });
  729. etile++;
  730. }
  731. }
  732. };
  733. this.clearUndos();
  734. this.clearRedos();
  735. comp(this.__LP, 0, false);
  736. comp(this.__RP, 256, ignoreTileZero);
  737. return this;
  738. }
  739. }
  740. NESBank.ACCESSMODE_8K = 0;
  741. NESBank.ACCESSMODE_1K = 1;
  742. NESBank.ACCESSMODE_2K = 2;
  743. NESBank.ACCESSMODE_4K = 3;
  744. NESBank.ACCESSMODE_1T = 4; // 8x8 Tile size
  745. NESBank.ACCESSMODE_2T = 5; // 8x16 Tile size