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

222 行
5.6KB

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