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

246 行
6.4KB

  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. this.__pixels = 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. if (!BLOCK_CHANGE_EMIT)
  67. obj.emit("data_changed");
  68. return true;
  69. }
  70. });
  71. }
  72. get width(){return 8;}
  73. get height(){return 8;}
  74. get pixels(){
  75. return this.__pixels;
  76. }
  77. get dataArray(){
  78. var d = [];
  79. for (var y = 0; y < 8; y++){
  80. for (var x = 0; x < 8; x++){
  81. d.push(this.getPixelIndex(x, y));
  82. }
  83. }
  84. return d;
  85. }
  86. get chr(){
  87. return new Uint8Array(this.__data);
  88. }
  89. set chr(buff){
  90. if (!(buff instanceof Uint8Array))
  91. throw new TypeError("Expected Uint8Array buffer");
  92. if (buff.length !== 16)
  93. throw new RangeError("Buffer contains invalid byte length.");
  94. this.__data = new Uint8Array(buff);
  95. this.emit("data_changed");
  96. }
  97. get base64(){
  98. var b = ""
  99. for (var i = 0; i < this.__data.length; i++) {
  100. b += String.fromCharCode(this.__data[i]);
  101. }
  102. b += String.fromCharCode(this.__paletteIndex);
  103. return window.btoa(b);
  104. }
  105. set base64(s){
  106. var b = window.atob(s);
  107. var len = b.length;
  108. if (b.length !== 17){
  109. throw new Error("Base64 string contains invalid byte count.");
  110. }
  111. var bytes = new Uint8Array(b.length-1);
  112. for (var i=0; i < b.length-1; i++){
  113. bytes[i] = b.charCodeAt(i);
  114. }
  115. this.__data = bytes;
  116. this.__paletteIndex = b.charCodeAt(b.length-1);
  117. this.emit("data_changed");
  118. }
  119. get paletteIndex(){return this.__paletteIndex;}
  120. set paletteIndex(pi){
  121. if (!Utils.isInt(pi))
  122. throw new TypeError("Palette index expected to be an integer.");
  123. if (pi < 0 || pi >= 4){
  124. throw new RangeError("Palette index out of bounds.");
  125. }
  126. this.__paletteIndex = pi;
  127. this.emit("data_changed");
  128. }
  129. setPixelIndex(x, y, ci){
  130. if (x < 0 || x >= 8 || y < 0 || y >= 8){
  131. throw new ValueError("Coordinates out of bounds.");
  132. }
  133. if (ci < 0 || ci >= 4){
  134. throw new ValueError("Color index out of bounds.");
  135. }
  136. this.__pixels[(y*8)+x] = ci;
  137. return this;
  138. }
  139. getPixelIndex(x, y){
  140. if (x < 0 || x >= 8 || y < 0 || y >= 8){
  141. throw new ValueError("Coordinates out of bounds.");
  142. }
  143. return this.__pixels[(8*y) + x];
  144. }
  145. flip(flag){
  146. if (flag >= 1 && flag <= 3){
  147. var oldData = this.__data;
  148. var newData = new Uint8Array(16);
  149. BLOCK_CHANGE_EMIT = true;
  150. for (var x = 0; x < 8; x++){
  151. for (var y = 0; y < 8; y++){
  152. this.__data = oldData;
  153. var ci = this.getPixelIndex(x, y);
  154. this.__data = newData;
  155. this.setPixelIndex(
  156. (flag == 1 || flag == 3) ? 7 - x : x,
  157. (flag == 2 || flag == 3) ? 7 - y : y,
  158. ci
  159. );
  160. }
  161. }
  162. BLOCK_CHANGE_EMIT = false;
  163. this.emit("data_changed");
  164. }
  165. return this;
  166. }
  167. clone(){
  168. return (new NESTile()).copy(this);
  169. }
  170. copy(t){
  171. if (!(t instanceof NESTile))
  172. throw new TypeError("Expected NESTile object.");
  173. this.__data.set(t.__data);
  174. this.emit("data_changed");
  175. return this;
  176. }
  177. clear(){
  178. this.__data = new Uint8Array(16);
  179. this.emit("data_changed");
  180. return this;
  181. }
  182. isEmpty(){
  183. for (let i=0; i < this.__data.length; i++){
  184. if (this.__data[i] !== 0)
  185. return false;
  186. }
  187. return true;
  188. }
  189. isEq(tile, sameOrientation){
  190. if (!(tile instanceof NESTile)){
  191. throw new TypeError("Expected NESTile instance.");
  192. }
  193. sameOrientation = (sameOrientation === true);
  194. var b64 = this.base64;
  195. if (tile.base64 === b64){
  196. return 0;
  197. }
  198. if (!sameOrientation){
  199. var tc = tile.clone().flip(1); // Flip horizontal.
  200. if (tc.base64 === b64){
  201. return 1;
  202. }
  203. tc.flip(3); // Flip horizontal AND verticle. Net effect is the same as tile.clone().flip(2) ... Flip Verticle
  204. if (tc.base64 === b64){
  205. return 2;
  206. }
  207. tc.flip(1); // Flip horizontal again. Net effect is the same as tile.clone().flip(3) ... flip H & V
  208. if (tc.base64 === b64){
  209. return 3;
  210. }
  211. }
  212. return -1;
  213. }
  214. }