A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

216 行
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. }