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.

216 lines
5.4KB

  1. import Utils from "/app/js/common/Utils.js";
  2. import NESPalette from "/app/js/models/NESPalette.js";
  3. function BitMask(offset){
  4. switch(offset){
  5. case 0:
  6. return 63; // Mask '00111111'
  7. case 1:
  8. return 207; // Mask '11001111'
  9. case 2:
  10. return 243; // Mask '11110011'
  11. }
  12. return 252; // Mask '11111100'
  13. }
  14. function SetDataArrayColor(arr, x, y, ci){
  15. var index = (y*8)+x;
  16. var dindex = Math.floor(index*0.25);
  17. var bitoffset = (index % 4);
  18. arr[dindex] = (arr[dindex] & BitMask(bitoffset)) ^ (ci << ((3 - bitoffset)*2));
  19. //if (dindex === 1){
  20. // console.log("index: ", dindex, " | value: ", arr[dindex], " | (x,y): (", x, ",", y, ") | Bit Offset: ", bitoffset, "Color: ", ci);
  21. //}
  22. }
  23. function GetDataArrayColor(arr, x, y){
  24. var index = (y*8)+x;
  25. var dindex = Math.floor(index*0.25);
  26. var bitoffset = 6 - ((index % 4) * 2);
  27. return (arr[dindex] & (3 << bitoffset)) >> bitoffset;
  28. }
  29. export default class NESTile{
  30. constructor(){
  31. this.__palette = null;
  32. this.__paletteIndex = 0;
  33. this.__data = new Uint8Array(16);
  34. }
  35. get pixels(){
  36. return new Proxy(this, {
  37. get: function(obj, prop){
  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.25);
  43. var bitoffset = 6 - ((prop % 4) * 2);
  44. return (obj.__data[dindex] & (3 << bitoffset)) >> bitoffset;
  45. },
  46. set: function(obj, prop, value){
  47. if (!Utils.isInt(prop))
  48. throw new TypeError("Expected integer index.");
  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(index*0.25);
  56. var bitoffset = (index % 4);
  57. obj.__data[dindex] = (obj.__data[dindex] & BitMask(bitoffset)) ^ (ci << ((3 - bitoffset)*2));
  58. }
  59. });
  60. }
  61. get dataArray(){
  62. var d = [];
  63. for (var y = 0; y < 8; y++){
  64. for (var x = 0; x < 8; x++){
  65. d.push(this.getPixelIndex(x, y));
  66. }
  67. }
  68. return d;
  69. }
  70. get base64(){
  71. var b = ""
  72. for (var i = 0; i < this.__data.length; i++) {
  73. b += String.fromCharCode(this.__data[i]);
  74. }
  75. return window.btoa(b);
  76. }
  77. set base64(s){
  78. var b = window.atob(s);
  79. var len = b.length;
  80. if (b.length !== 16){
  81. throw new Error("Base64 string contains invalid byte count.");
  82. }
  83. var bytes = new Uint8Array(b.length);
  84. for (var i=0; i < b.length; i++){
  85. bytes[i] = b.charCodeAt(i);
  86. }
  87. this.__data = bytes;
  88. }
  89. get palette(){return this.__palette;}
  90. set palette(p){
  91. if (p !== null && !(p instanceof NESPalette)){
  92. throw new TypeError("Expected NESPalette instance or null.");
  93. }
  94. this.__palette = p;
  95. }
  96. get paletteIndex(){return this.__paletteIndex;}
  97. set paletteIndex(pi){
  98. if (!Utils.isInt(pi))
  99. throw new TypeError("Palette index expected to be an integer.");
  100. if (pi < 0 || pi >= 4){
  101. throw new RangeError("Palette index out of bounds.");
  102. }
  103. this.__paletteIndex = pi;
  104. }
  105. setPixelIndex(x, y, ci){
  106. if (x < 0 || x >= 8 || y < 0 || y >= 8){
  107. throw new ValueError("Coordinates out of bounds.");
  108. }
  109. if (ci < 0 || ci >= 4){
  110. throw new ValueError("Color index out of bounds.");
  111. }
  112. SetDataArrayColor(this.__data, x, y, ci);
  113. return this;
  114. }
  115. getPixelIndex(x, y){
  116. if (x < 0 || x >= 8 || y < 0 || y >= 8){
  117. throw new ValueError("Coordinates out of bounds.");
  118. }
  119. return GetDataArrayColor(this.__data, x, y);
  120. }
  121. getPixel(x, y){
  122. var ci = 0;
  123. try {
  124. ci = this.getPixelIndex(x, y);
  125. } catch (e) {
  126. throw e;
  127. }
  128. if (this.__palette !== null){
  129. return this.__palette.get_palette_color(this.__paletteIndex, ci);
  130. }
  131. switch(ci){
  132. case 1:
  133. return "#555555";
  134. case 2:
  135. return "#AAAAAA";
  136. case 3:
  137. return "#FFFFFF";
  138. }
  139. return 0;
  140. }
  141. flip(flag){
  142. if (flag >= 1 && flag <= 3){
  143. var newData = new Uint8Array(16);
  144. for (var x = 0; x < 8; x++){
  145. for (var y = 0; y < 8; y++){
  146. var ci = GetDataArrayColor(this.__data, x, y);
  147. SetDataArrayColor(
  148. newData,
  149. (flag == 1 || flag == 3) ? 7 - x: x,
  150. (flag == 2 || flag == 3) ? 7 - y: y,
  151. ci
  152. );
  153. //console.log(newData);
  154. //newData[r[0]] = 2;
  155. //newData[r[0]] = r[1];
  156. }
  157. }
  158. //console.log(newData);
  159. this.__data = newData;
  160. }
  161. return this;
  162. }
  163. clone(){
  164. var t = new NESTile();
  165. t.base64 = this.base64;
  166. return t;
  167. }
  168. isEq(tile){
  169. if (!(tile instanceof NESTile)){
  170. throw new TypeError("Expected NESTile instance.");
  171. }
  172. var b64 = this.base64;
  173. if (tile.base64 === b64){
  174. return 0;
  175. }
  176. var tc = tile.clone().flip(1); // Flip horizontal.
  177. if (tc.base64 === b64){
  178. return 1;
  179. }
  180. tc.flip(3); // Flip horizontal AND verticle. Net effect is the same as tile.clone().flip(2) ... Flip Verticle
  181. if (tc.base64 === b64){
  182. return 2;
  183. }
  184. tc.flip(1); // Flip horizontal again. Net effect is the same as tile.clone().flip(3) ... flip H & V
  185. if (tc.base64 === b64){
  186. return 3;
  187. }
  188. return -1;
  189. }
  190. }