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

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