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文字以内のものにしてください。

NESPalette.js 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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. }