A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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