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

256 行
6.9KB

  1. import Utils from "/app/js/common/Utils.js";
  2. import ISurface from "/app/js/ifaces/ISurface.js";
  3. import NESTile from "/app/js/models/NESTile.js";
  4. import NESPalette from "/app/js/models/NESPalette.js";
  5. function LRIdx2TileIdxCo(index, lid){
  6. lid || (lid = 2);
  7. var res = {
  8. lid: 0,
  9. index: 0,
  10. x: 0,
  11. y: 0
  12. };
  13. var w = (lid == 2) ? 256 : 128;
  14. var x = Math.floor(index % w);
  15. var y = Math.floor(index / w);
  16. if (x < 128){
  17. res.index = (Math.floor(y/8) * 16) + Math.floor(x / 8);
  18. if (lid !== 2)
  19. res.lid = lid;
  20. } else {
  21. res.index = (Math.floor(y/8) * 16) + Math.floor((x - 128) / 8);
  22. res.lid = 1;
  23. }
  24. res.x = x % 8;
  25. res.y = y % 8;
  26. return res;
  27. }
  28. export default class NESBank extends ISurface{
  29. constructor(){
  30. super();
  31. this.__LP = []; // Left Patterns (Sprites)
  32. this.__RP = []; // Right Patterns (Backgrounds)
  33. this.__AccessMode = 2; // 0 = Sprites only | 1 = BG only | 2 = Sprites and BG
  34. for (var i=0; i < 256; i++){
  35. this.__LP.push(new NESTile());
  36. this.__RP.push(new NESTile());
  37. }
  38. this.__palette = null;
  39. }
  40. get access_mode(){return this.__AccessMode;}
  41. set access_mode(m){
  42. if (!Utils.isInt(m))
  43. throw new TypeError("Access mode expected to be integer.");
  44. switch(m){
  45. case NESBank.ACCESSMODE_SPRITE:
  46. this.__AccessMode = NESBank.ACCESSMODE_SPRITE; break;
  47. case NESBank.ACCESSMODE_BACKGROUND:
  48. this.__AccessMode = NESBank.ACCESSMODE_BACKGROUND; break;
  49. case NESBank.ACCESSMODE_FULL:
  50. this.__AccessMode = NESBank.ACCESSMODE_FULL; break;
  51. }
  52. }
  53. get json(){
  54. JSON.stringify({
  55. LP: this.__LP.map(x=>x.base64),
  56. RP: this.__RP.map(x=>x.base64)
  57. });
  58. }
  59. get chr(){
  60. var buff = new Uint8Array(8192);
  61. var offset = 0;
  62. this.__LP.forEach(function(i){
  63. buff.set(i.chr, offset);
  64. offset += 16;
  65. });
  66. this.__RP.forEach(function(i){
  67. buff.set(i.chr, offset);
  68. offset += 16;
  69. });
  70. return buff;
  71. }
  72. get palette(){return this.__palette;}
  73. set palette(p){
  74. if (p !== null && !(p instanceof NESPalette))
  75. throw new TypeError("Expected null or NESPalette object.");
  76. if (p !== this.__palette){
  77. this.__palette = p;
  78. }
  79. }
  80. get width(){return (this.__AccessMode == NESBank.ACCESSMODE_FULL) ? 256 : 128;}
  81. get height(){return 128;}
  82. get length(){return this.width * this.height;}
  83. get coloridx(){
  84. return new Proxy(this, {
  85. get:function(obj, prop){
  86. var len = obj.length * 8;
  87. if (prop === "length")
  88. return len;
  89. if (!Utils.isInt(prop))
  90. throw new TypeError("Expected integer index.");
  91. prop = parseInt(prop);
  92. if (prop < 0 || prop >= len)
  93. return NESPalette.Default[4];
  94. var res = LRIdx2TileIdxCo(prop, this.__AccessMode);
  95. var list = (res.lid === 0) ? obj.__LP : obj.__RP;
  96. return list[res.index].getPixelIndex(res.x, res.y);
  97. },
  98. set:function(obj, prop, value){
  99. if (!Utils.isInt(prop))
  100. throw new TypeError("Expected integer index.");
  101. if (!Utils.isInt(value))
  102. throw new TypeError("Color expected to be integer.");
  103. prop = parseInt(prop);
  104. value = parseInt(value);
  105. if (prop < 0 || prop >= len)
  106. throw new RangeError("Index out of bounds.");
  107. if (value < 0 || value >= 4)
  108. throw new RangeError("Color index out of bounds.");
  109. var res = LRIdx2TileIdxCo(prop, this.__AccessMode);
  110. var list = (res.lid === 0) ? obj.__LP : obj.__RP;
  111. list[res.index].setPixelIndex(res.x, res.y, value);
  112. return true;
  113. }
  114. });
  115. }
  116. get lp(){
  117. return new Proxy(this, {
  118. get: function(obj, prop){
  119. if (prop === "length")
  120. return obj.__LP.length;
  121. if (!Utils.isInt(prop))
  122. throw new TypeError("Expected integer index.");
  123. prop = parseInt(prop);
  124. if (prop < 0 || prop >= 256)
  125. throw new RangeError("Index out of bounds.");
  126. return obj.__LP[prop];
  127. },
  128. set: function(obj, prop, value){
  129. if (!Utils.isInt(prop))
  130. throw new TypeError("Expected integer index.");
  131. if (!(value instanceof NESTile))
  132. throw new TypeError("Can only assign NESTile objects.");
  133. prop = parseInt(prop);
  134. if (prop < 0 || prop >= 256)
  135. throw new RangeError("Index out of bounds.");
  136. obj.__LP[prop].copy(value);
  137. return true;
  138. }
  139. });
  140. }
  141. get rp(){
  142. return new Proxy(this, {
  143. get: function(obj, prop){
  144. if (prop === "length")
  145. return obj.__RP.length;
  146. if (!Utils.isInt(prop))
  147. throw new TypeError("Expected integer index.");
  148. prop = parseInt(prop);
  149. if (prop < 0 || prop >= 256)
  150. throw new RangeError("Index out of bounds.");
  151. return obj.__RP[prop];
  152. },
  153. set: function(obj, prop, value){
  154. if (!Utils.isInt(prop))
  155. throw new TypeError("Expected integer index.");
  156. if (!(value instanceof NESTile))
  157. throw new TypeError("Can only assign NESTile objects.");
  158. prop = parseInt(prop);
  159. if (prop < 0 || prop >= 256)
  160. throw new RangeError("Index out of bounds.");
  161. obj.__RP[prop].copy(value);
  162. return true;
  163. }
  164. });
  165. }
  166. copy(b){
  167. if (!(b instanceof NESBank))
  168. throw new TypeError("Expected NESBank object.");
  169. for (var i=0; i < 256; i++){
  170. this.lp[i] = b.lp[i];
  171. this.rp[i] = b.rp[i];
  172. }
  173. return this;
  174. }
  175. clone(){
  176. return (new NESBank()).copy(this);
  177. }
  178. getColor(x,y){
  179. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  180. return this.__default_pi[4];
  181. var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode);
  182. var list = (res.lid === 0) ? this.__LP : this.__RP;
  183. var pi = list[res.index].paletteIndex;
  184. var ci = list[res.index].getPixelIndex(res.x, res.y);
  185. if (this.__palette !== null){
  186. return this.__palette.get_palette_color(pi, ci);
  187. }
  188. return NESPalette.Default[ci];
  189. }
  190. getColorIndex(x, y){
  191. if (x < 0 || x >= this.width || y < 0 || y >= this.height)
  192. return {pi: -1, ci:-1};
  193. var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode);
  194. var list = (res.lid === 0) ? this.__LP : this.__RP;
  195. return {
  196. pi: list[res.index].paletteIndex,
  197. ci: list[res.index].getPixelIndex(res.x, res.y)
  198. };
  199. }
  200. setColorIndex(x, y, ci, pi){
  201. if (x < 0 || x >= this.width || y < 0 || y > this.height)
  202. throw new RangeError("Coordinates out of bounds.");
  203. if (!Utils.isInt(pi))
  204. pi = -1;
  205. if (!Utils.isInt(ci))
  206. ci = 0;
  207. if (pi < 0){
  208. this.coloridx[(y*this.width)+x] = ci;
  209. } else {
  210. var res = LRIdx2TileIdxCo((y*this.width)+x, this.__AccessMode);
  211. var list = (res.lid === 0) ? this.__LP : this.__RP;
  212. list[res.index].paletteIndex = pi;
  213. list[res.index].setPixelIndex(res.x, res.y, ci);
  214. }
  215. return this;
  216. }
  217. }
  218. NESBank.ACCESSMODE_SPRITE = 0;
  219. NESBank.ACCESSMODE_BACKGROUND = 1;
  220. NESBank.ACCESSMODE_FULL = 2;