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

227 行
6.0KB

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