A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

246 lines
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. }