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个字符

310 行
8.2KB

  1. import {EventCaller} from "/app/js/common/EventCaller.js";
  2. import JSONSchema from "/app/js/common/JSONSchema.js";
  3. const SCHEMA_ID="http://nespaint/NESPaletteSchema.json";
  4. JSONSchema.add({
  5. "$schema": "http://json-schema.org/draft-07/schema#",
  6. "$id": SCHEMA_ID,
  7. "type":"array",
  8. "minItems":25,
  9. "maxItems":25,
  10. "items":{
  11. "type":"number",
  12. "minimum": 0,
  13. "exclusiveMaximum": 64
  14. }
  15. });
  16. /**
  17. * Object for manipulating the eight NES palettes.
  18. * @extends EventCaller
  19. */
  20. export default class NESPalette extends EventCaller{
  21. constructor(){
  22. super();
  23. this.__BGColor = 63; // Index to the background color ALL palettes MUST share.
  24. this.__palette = [
  25. // Tile/Background Palettes
  26. 0,0,0,
  27. 0,0,0,
  28. 0,0,0,
  29. 0,0,0,
  30. // Sprite Palettes
  31. 0,0,0,
  32. 0,0,0,
  33. 0,0,0,
  34. 0,0,0
  35. ];
  36. }
  37. get obj(){
  38. return ([this.__BGColor]).concat(this.__palette);
  39. }
  40. set obj(d){
  41. var validator = JSONSchema.getValidator(SCHEMA_ID);
  42. if (validator !== null && validator(d)){
  43. this.set_palette(d);
  44. } else {
  45. throw new Error("Object failed to validate against NESPaletteSchema");
  46. }
  47. }
  48. get json(){
  49. return JSON.stringify(this.obj);
  50. }
  51. set json(j){
  52. try {
  53. this.obj = JSON.parse(j);
  54. } catch (e) {
  55. throw e;
  56. }
  57. }
  58. /**
  59. * Sets one or all of the eight color palettes to the values given. By default, function
  60. * assumes the given array is for all eight palettes (or 25 total color indexes, 3 per palette
  61. * and 1 background/transparency color used by ALL palettes).
  62. * If a single palette is being set, the array must only contain 3 entries.
  63. * @param {Array} apci - Array of color indexes to store into the palette(s)
  64. * @param {number} [p=8] - Zero-based index of the palette being set. Any value outside the range of 0 - 7 will set ALL palettes.
  65. * @returns {this}
  66. */
  67. set_palette(apci, p=8){
  68. var StoreColorValue = (i, v) => {
  69. if (typeof(v) == 'number'){
  70. if (i >= 0)
  71. this.__palette[i] = v
  72. else
  73. this.__BGColor = v;
  74. } else if (typeof(v) == 'string' && v.length == 2){
  75. var c = parseInt(v, 16);
  76. if (!isNaN(c)){
  77. if (i >= 0)
  78. this.__palette[i] = c;
  79. else
  80. this.__BGColor = c;
  81. }
  82. }
  83. };
  84. if (typeof(p) != 'number')
  85. throw new TypeError("First argument expected to be a number.");
  86. if (!(apci instanceof Array))
  87. throw new TypeError("Expected an array of color index values.");
  88. if (p < 0 || p >= 8){ // Setting ALL palettes!
  89. if (apci.length != 25)
  90. throw new RangeError("Color array must contain 25 color values to fill all palettes.");
  91. StoreColorValue(-1, apci[0]);
  92. for (var i=0; i < 24; i++){
  93. StoreColorValue(i, apci[i+1]);
  94. }
  95. } else { // Setting a specific palette.
  96. if (apci.length != 3)
  97. throw new RangeError("Color array must contain three color values.");
  98. p *= 3;
  99. for (var i=0; i < 4; i++){
  100. StoreColorValue(p+i, apci[i]);
  101. }
  102. }
  103. this.emit("palettes_changed", {type:"ALL"});
  104. return this;
  105. }
  106. /**
  107. * Sets a palette's color index value to a given system color index.
  108. * NOTE: Setting palette color index 0 for ANY palette changes that index for ALL palettes.
  109. * @param {number} p - The index of the palette being set.
  110. * @param {number} pci - The palette color index (0 - 3) to set.
  111. * @param {number} sci - The system color index (0 - 63) value to set to.
  112. * @returns {this}
  113. */
  114. set_palette_syscolor_index(p, pci, sci){
  115. if (typeof(p) != 'number' || typeof(pci) != 'number' || typeof(sci) != 'number')
  116. throw new TypeError("Palette, palette color, and system color index expected to be numbers.");
  117. if (p < 0 || p >= 8){
  118. throw new RangeError("Palette index is out of bounds.");
  119. }
  120. if (pci < 0 || pci >= 4){
  121. throw new RangeError("Palette color index is out of bounds.");
  122. }
  123. if (typeof(sci) == "string" && sci.length == 2)
  124. sci = parseInt(sci, 16);
  125. if (isNaN(sci))
  126. throw new TypeError("System Color Index expected to be a number of hex value string.");
  127. if (sci < 0 || sci >= 64){
  128. throw new RangeError("System color index is out of bounds.");
  129. }
  130. if (pci == 0){
  131. this.__BGColor = sci;
  132. this.emit("palettes_changed", {type:"ALL", cindex:0});
  133. } else {
  134. this.__palette[(p*3) + (pci-1)] = sci;
  135. this.emit("palettes_changed", {type:(p < 4) ? "TILE" : "SPRITE", pindex:p, cindex:pci});
  136. }
  137. return this;
  138. }
  139. /**
  140. * Returns the system color index at the given palette color index.
  141. * @param {number} p - The index (0 - 7) of the palette.
  142. * @param {number} pci - The palette color index (0 - 3).
  143. * @param {boolean} [ashex=false] - If true, will return the index as a two character hex string.
  144. * @returns {number} - The index of the system color used.
  145. */
  146. get_palette_syscolor_index(p, pci, ashex=false){
  147. if (typeof(p) != 'number' || typeof(pci) != 'number')
  148. throw new TypeError("Palette and color index expected to be numbers.");
  149. if (p < 0 || p >= 8){
  150. throw new RangeError("Palette index is out of bounds.");
  151. }
  152. if (pci < 0 || pci >= 4){
  153. throw new RangeError("Palette color index is out of bounds.");
  154. }
  155. var i = (pci === 0) ? this.__BGColor : this.__palette[(p*3)+(pci-1)];
  156. if (ashex){
  157. i = i.toString(16);
  158. i = ((i.length < 2) ? "0" : "") + i;
  159. }
  160. return i;
  161. }
  162. /**
  163. * Returns a hex string color value used by the NES system at the index stored at the given
  164. * palette color index.
  165. * @param {number} p - The index (0 - 7) of the palette.
  166. * @param {number} pci - The palette color index (0 - 3).
  167. * @returns {string}
  168. */
  169. get_palette_color(p, pci){
  170. if (typeof(p) != 'number' || typeof(pci) != 'number')
  171. throw new TypeError("Palette and color index expected to be numbers.");
  172. if (p < 0 || p >= 8){
  173. throw new RangeError("Palette index is out of bounds.");
  174. }
  175. if (pci < 0 || pci >= 4){
  176. throw new RangeError("Palette color index is out of bounds.");
  177. }
  178. return NESPalette.SystemColor[this.get_palette_syscolor_index(p, pci)];
  179. }
  180. /**
  181. * Generates a small 6502 assembly block string containing the current palette data.
  182. * @param {string} [memname="PaletteData"] The label named under which to store the data.
  183. * @returns {string}
  184. */
  185. to_asm(memname="PaletteData"){
  186. var NumToHex=function(n){
  187. var h = n.toString(16);
  188. if (h.length %2)
  189. h = '0' + h;
  190. return '$' + h;
  191. };
  192. var BGHex = NumToHex(this.__BGColor);
  193. var s = memname + ":\n\t.db ";
  194. // Storing background palette data.
  195. for (var i=0; i < 12; i++){
  196. if (i % 3 == 0)
  197. s += ((i == 0) ? "" : " ") + BGHex;
  198. s += " " + NumToHex(this.__palette[i]);
  199. }
  200. s += "\t; Background palette data.\n\t.db ";
  201. // Storing foreground palette data.
  202. for (var i=12; i < 24; i++){
  203. if (i % 3 == 0)
  204. s += ((i == 12) ? "" : " ") + BGHex;
  205. s += " " + NumToHex(this.__palette[i]);
  206. }
  207. s += "\t; Foreground palette data.";
  208. return s;
  209. }
  210. }
  211. // NES Palette color information comes from the following site...
  212. // http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php/NES_Palette
  213. /**
  214. * Hex string color values representing the NES system palette.
  215. */
  216. NESPalette.SystemColor = [
  217. "#7C7C7C",
  218. "#0000FC",
  219. "#0000BC",
  220. "#4428BC",
  221. "#940084",
  222. "#A80020",
  223. "#A81000",
  224. "#881400",
  225. "#503000",
  226. "#007800",
  227. "#006800",
  228. "#005800",
  229. "#004058",
  230. "#000000",
  231. "#000000",
  232. "#000000",
  233. "#BCBCBC",
  234. "#0078F8",
  235. "#0058F8",
  236. "#6844FC",
  237. "#D800CC",
  238. "#E40058",
  239. "#F83800",
  240. "#E45C10",
  241. "#AC7C00",
  242. "#00B800",
  243. "#00A800",
  244. "#00A844",
  245. "#008888",
  246. "#000000",
  247. "#000000",
  248. "#000000",
  249. "#F8F8F8",
  250. "#3CBCFC",
  251. "#6888FC",
  252. "#9878F8",
  253. "#F878F8",
  254. "#F85898",
  255. "#F87858",
  256. "#FCA044",
  257. "#F8B800",
  258. "#B8F818",
  259. "#58D854",
  260. "#58F898",
  261. "#00E8D8",
  262. "#787878",
  263. "#000000",
  264. "#000000",
  265. "#FCFCFC",
  266. "#A4E4FC",
  267. "#B8B8F8",
  268. "#D8B8F8",
  269. "#F8B8F8",
  270. "#F8A4C0",
  271. "#F0D0B0",
  272. "#FCE0A8",
  273. "#F8D878",
  274. "#D8F878",
  275. "#B8F8B8",
  276. "#B8F8D8",
  277. "#00FCFC",
  278. "#F8D8F8",
  279. "#000000",
  280. "#000000"
  281. ];
  282. NESPalette.Default = [
  283. "#080808",
  284. "#343434",
  285. "#a2a2a2",
  286. "#efefef",
  287. "#666666" // Out of bounds color.
  288. ];