A pixel art painter geared specifically at NES pixel art. Includes export for .chr binary file as well as palette and namespace data.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

335 lines
8.8KB

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