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

202 行
5.2KB

  1. import Utils from "/app/js/common/Utils.js";
  2. import NESPalette from "/app/js/models/NESPalette.js";
  3. function BitMask(offset, inv){
  4. switch(offset){
  5. case 0:
  6. return parseInt((inv === true) ? '01111111' : '10000000', 2);
  7. case 1:
  8. return parseInt((inv === true) ? '10111111' : '01000000', 2);
  9. case 2:
  10. return parseInt((inv === true) ? '11011111' : '00100000', 2);
  11. case 3:
  12. return parseInt((inv === true) ? '11101111' : '00010000', 2);
  13. case 4:
  14. return parseInt((inv === true) ? '11110111' : '00001000', 2);
  15. case 5:
  16. return parseInt((inv === true) ? '11111011' : '00000100', 2);
  17. case 6:
  18. return parseInt((inv === true) ? '11111101' : '00000010', 2);
  19. }
  20. return parseInt((inv === true) ? '11111110' : '00000001', 2);
  21. }
  22. export default class NESTile{
  23. constructor(){
  24. this.__paletteIndex = 0;
  25. this.__data = new Uint8Array(16);
  26. }
  27. get pixels(){
  28. return new Proxy(this, {
  29. get: function(obj, prop){
  30. if (prop === "length")
  31. return 64;
  32. if (!Utils.isInt(prop))
  33. throw new TypeError("Expected integer index.");
  34. if (prop < 0 || prop >= 64)
  35. throw new RangeError("Index out of bounds.");
  36. var dindex = Math.floor(prop*0.125);
  37. var bitoffset = 7 - (prop%8);
  38. var v = (obj.__data[dindex] & (1 << bitoffset)) >> bitoffset;
  39. v += 2*((obj.__data[8+dindex] & (1 << bitoffset)) >> bitoffset);
  40. return v;
  41. },
  42. set: function(obj, prop, value){
  43. if (!Utils.isInt(prop))
  44. throw new TypeError("Expected integer index.");
  45. prop = parseInt(prop);
  46. if (!Utils.isInt(value))
  47. throw new TypeError("Color index expected to be integer.");
  48. if (prop < 0 || prop >= 64)
  49. throw new RangeError("Index out of bounds.");
  50. if (value < 0 || value >= 4)
  51. throw new RangeError("Color index out of bounds.");
  52. var dindex = Math.floor(prop*0.125);
  53. var bitoffset = (prop % 8);
  54. if (value == 1 || value == 3){
  55. obj.__data[dindex] |= BitMask(bitoffset);
  56. } else {
  57. obj.__data[dindex] &= BitMask(bitoffset, true);
  58. }
  59. if (value == 2 || value == 3){
  60. obj.__data[8+dindex] |= BitMask(bitoffset);
  61. } else {
  62. obj.__data[8+dindex] &= BitMask(bitoffset, true);
  63. }
  64. return true;
  65. }
  66. });
  67. }
  68. get dataArray(){
  69. var d = [];
  70. for (var y = 0; y < 8; y++){
  71. for (var x = 0; x < 8; x++){
  72. d.push(this.getPixelIndex(x, y));
  73. }
  74. }
  75. return d;
  76. }
  77. get chr(){
  78. return new Uint8Array(this.__data);
  79. }
  80. get base64(){
  81. var b = ""
  82. for (var i = 0; i < this.__data.length; i++) {
  83. b += String.fromCharCode(this.__data[i]);
  84. }
  85. b += String.fromCharCode(this.__paletteIndex);
  86. return window.btoa(b);
  87. }
  88. set base64(s){
  89. var b = window.atob(s);
  90. var len = b.length;
  91. if (b.length !== 17){
  92. throw new Error("Base64 string contains invalid byte count.");
  93. }
  94. var bytes = new Uint8Array(b.length-1);
  95. for (var i=0; i < b.length-1; i++){
  96. bytes[i] = b.charCodeAt(i);
  97. }
  98. this.__data = bytes;
  99. this.__paletteIndex = b.charCodeAt(b.length-1);
  100. }
  101. get paletteIndex(){return this.__paletteIndex;}
  102. set paletteIndex(pi){
  103. if (!Utils.isInt(pi))
  104. throw new TypeError("Palette index expected to be an integer.");
  105. if (pi < 0 || pi >= 4){
  106. throw new RangeError("Palette index out of bounds.");
  107. }
  108. this.__paletteIndex = pi;
  109. }
  110. setPixelIndex(x, y, ci){
  111. if (x < 0 || x >= 8 || y < 0 || y >= 8){
  112. throw new ValueError("Coordinates out of bounds.");
  113. }
  114. if (ci < 0 || ci >= 4){
  115. throw new ValueError("Color index out of bounds.");
  116. }
  117. this.pixels[(y*8)+x] = ci;
  118. return this;
  119. }
  120. getPixelIndex(x, y){
  121. if (x < 0 || x >= 8 || y < 0 || y >= 8){
  122. throw new ValueError("Coordinates out of bounds.");
  123. }
  124. return this.pixels[(8*y) + x];
  125. }
  126. flip(flag){
  127. if (flag >= 1 && flag <= 3){
  128. var oldData = this.__data;
  129. var newData = new Uint8Array(16);
  130. for (var x = 0; x < 8; x++){
  131. for (var y = 0; y < 8; y++){
  132. this.__data = oldData;
  133. var ci = this.getPixelIndex(x, y);
  134. this.__data = newData;
  135. this.setPixelIndex(
  136. (flag == 1 || flag == 3) ? 7 - x : x,
  137. (flag == 2 || flag == 3) ? 7 - y : y,
  138. ci
  139. );
  140. }
  141. }
  142. }
  143. return this;
  144. }
  145. clone(){
  146. return (new NESTile()).copy(this);
  147. }
  148. copy(t){
  149. if (!(t instanceof NESTile))
  150. throw new TypeError("Expected NESTile object.");
  151. this.__data.set(t.__data);
  152. return this;
  153. }
  154. isEq(tile){
  155. if (!(tile instanceof NESTile)){
  156. throw new TypeError("Expected NESTile instance.");
  157. }
  158. var b64 = this.base64;
  159. if (tile.base64 === b64){
  160. return 0;
  161. }
  162. var tc = tile.clone().flip(1); // Flip horizontal.
  163. if (tc.base64 === b64){
  164. return 1;
  165. }
  166. tc.flip(3); // Flip horizontal AND verticle. Net effect is the same as tile.clone().flip(2) ... Flip Verticle
  167. if (tc.base64 === b64){
  168. return 2;
  169. }
  170. tc.flip(1); // Flip horizontal again. Net effect is the same as tile.clone().flip(3) ... flip H & V
  171. if (tc.base64 === b64){
  172. return 3;
  173. }
  174. return -1;
  175. }
  176. }