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.

200 lines
4.8KB

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