A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

200 Zeilen
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. }