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

205 行
5.3KB

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