├── .gitignore ├── AEScripts ├── AutoColorChartBeta_CN.jsx ├── AutoColorChartBeta_JP.jsx └── LICENSE ├── LICENSE ├── README.en.md ├── README.md ├── README.zh.md ├── _acc1.0 ├── AutoColorChart.py └── AutoColorChartGUI.py ├── sampleDatas ├── color_chart_sample_boxes.png.json ├── marked_colors.JPG ├── sample_boxes.png └── 淮_sample_data │ ├── 001_01_huai_normal.png │ ├── 001_01_huai_normal.png_marked_colors.png │ ├── 001_01_huai_normal_same_color.png │ ├── color_chart_001_01_huai_normal.png.json │ └── color_chart_001_01_huai_normal.png.xml ├── screenshoots ├── 2.4main.PNG ├── aescriptmain.png ├── aescripttypepanel.png ├── colorpicker.gif ├── configjson.png ├── debug.png ├── func.png ├── json.png ├── mian.png ├── mian_cn.png ├── usrboxmgn.png ├── usrboxreg.png └── xml.png └── supportedBoxes ├── 00_04.PNG ├── 00_05.PNG ├── 00_07.PNG ├── 01_04.PNG ├── 02_00.PNG └── 03_00.PNG /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | sampleDatas/.DS_Store 3 | -------------------------------------------------------------------------------- /AEScripts/AutoColorChartBeta_CN.jsx: -------------------------------------------------------------------------------- 1 | //===========================================================================================// 2 | // Script Name: AutoColorChart.jsx 3 | // Version: 2.23 beta 4 | // Author: 千石まよひ 5 | // Last Update: 2024/05/29 6 | // License: MIT 7 | // Copyright 2024 SengokuMayoi (Ma Chenxing) 8 | //============================================================================================// 9 | // 为ES3系统带来JSON对象支持,包含 JSON.parse() 和 JSON.stringify() 方法 10 | // =================================================================== 11 | 12 | if (typeof JSON !== "object") { 13 | JSON = {}; 14 | } 15 | 16 | (function() { 17 | "use strict"; 18 | 19 | var rx_one = /^[\],:{}\s]*$/; 20 | var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; 21 | var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; 22 | var rx_four = /(?:^|:|,)(?:\s*\[)+/g; 23 | var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 24 | var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 25 | 26 | function f(n) { 27 | // Format integers to have at least two digits. 28 | return (n < 10) ? "0" + n: n; 29 | } 30 | 31 | function this_value() { 32 | return this.valueOf(); 33 | } 34 | 35 | if (typeof Date.prototype.toJSON !== "function") { 36 | 37 | Date.prototype.toJSON = function() { 38 | 39 | return isFinite(this.valueOf()) ? (this.getUTCFullYear() + "-" + f(this.getUTCMonth() + 1) + "-" + f(this.getUTCDate()) + "T" + f(this.getUTCHours()) + ":" + f(this.getUTCMinutes()) + ":" + f(this.getUTCSeconds()) + "Z") : null; 40 | }; 41 | 42 | Boolean.prototype.toJSON = this_value; 43 | Number.prototype.toJSON = this_value; 44 | String.prototype.toJSON = this_value; 45 | } 46 | 47 | var gap; 48 | var indent; 49 | var meta; 50 | var rep; 51 | 52 | function quote(string) { 53 | 54 | // If the string contains no control characters, no quote characters, and no 55 | // backslash characters, then we can safely slap some quotes around it. 56 | // Otherwise we must also replace the offending characters with safe escape 57 | // sequences. 58 | rx_escapable.lastIndex = 0; 59 | return rx_escapable.test(string) ? "\"" + string.replace(rx_escapable, 60 | function(a) { 61 | var c = meta[a]; 62 | return typeof c === "string" ? c: "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice( - 4); 63 | }) + "\"": "\"" + string + "\""; 64 | } 65 | 66 | // This variable is initialized with an empty array every time 67 | // JSON.stringify() is invoked and checked by the str() function. It's 68 | // used to keep references to object structures and capture cyclic 69 | // objects. Every new object is checked for its existence in this 70 | // array. If it's found it means the JSON object is cyclic and we have 71 | // to stop execution and throw a TypeError accordingly the ECMA262 72 | // (see NOTE 1 by the link https://tc39.es/ecma262/#sec-json.stringify). 73 | var seen; 74 | 75 | // Emulate [].includes(). It's actual for old-fashioned JScript. 76 | function includes(array, value) { 77 | var i; 78 | for (i = 0; i < array.length; i += 1) { 79 | if (value === array[i]) { 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | function str(key, holder) { 87 | 88 | // Produce a string from holder[key]. 89 | var i; // The loop counter. 90 | var k; // The member key. 91 | var v; // The member value. 92 | var length; 93 | var mind = gap; 94 | var partial; 95 | var value = holder[key]; 96 | 97 | // If the value has a toJSON method, call it to obtain a replacement value. 98 | if (value && typeof value === "object" && typeof value.toJSON === "function") { 99 | value = value.toJSON(key); 100 | } 101 | 102 | // If we were called with a replacer function, then call the replacer to 103 | // obtain a replacement value. 104 | if (typeof rep === "function") { 105 | value = rep.call(holder, key, value); 106 | } 107 | 108 | // What happens next depends on the value's type. 109 | switch (typeof value) { 110 | case "string": 111 | return quote(value); 112 | 113 | case "number": 114 | 115 | // JSON numbers must be finite. Encode non-finite numbers as null. 116 | return (isFinite(value)) ? String(value) : "null"; 117 | 118 | case "boolean": 119 | case "null": 120 | 121 | // If the value is a boolean or null, convert it to a string. Note: 122 | // typeof null does not produce "null". The case is included here in 123 | // the remote chance that this gets fixed someday. 124 | return String(value); 125 | 126 | // If the type is "object", we might be dealing with an object or an array or 127 | // null. 128 | case "object": 129 | 130 | // Due to a specification blunder in ECMAScript, typeof null is "object", 131 | // so watch out for that case. 132 | if (!value) { 133 | return "null"; 134 | } 135 | 136 | // Check the value is not circular object. Otherwise throw TypeError. 137 | if (includes(seen, value)) { 138 | throw new TypeError("Converting circular structure to JSON"); 139 | } 140 | 141 | // Keep the value for the further check on circular references. 142 | seen.push(value); 143 | 144 | // Make an array to hold the partial results of stringifying this object value. 145 | gap += indent; 146 | partial = []; 147 | 148 | // Is the value an array? 149 | if (Object.prototype.toString.apply(value) === "[object Array]") { 150 | 151 | // The value is an array. Stringify every element. Use null as a placeholder 152 | // for non-JSON values. 153 | length = value.length; 154 | for (i = 0; i < length; i += 1) { 155 | partial[i] = str(i, value) || "null"; 156 | } 157 | 158 | // Join all of the elements together, separated with commas, and wrap them in 159 | // brackets. 160 | v = partial.length === 0 ? "[]": gap ? ("[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]") : "[" + partial.join(",") + "]"; 161 | gap = mind; 162 | return v; 163 | } 164 | 165 | // If the replacer is an array, use it to select the members to be stringified. 166 | if (rep && typeof rep === "object") { 167 | length = rep.length; 168 | for (i = 0; i < length; i += 1) { 169 | if (typeof rep[i] === "string") { 170 | k = rep[i]; 171 | v = str(k, value); 172 | if (v) { 173 | partial.push(quote(k) + ((gap) ? ": ": ":") + v); 174 | } 175 | } 176 | } 177 | } else { 178 | 179 | // Otherwise, iterate through all of the keys in the object. 180 | for (k in value) { 181 | if (Object.prototype.hasOwnProperty.call(value, k)) { 182 | v = str(k, value); 183 | if (v) { 184 | partial.push(quote(k) + ((gap) ? ": ": ":") + v); 185 | } 186 | } 187 | } 188 | } 189 | 190 | // Join all of the member texts together, separated with commas, 191 | // and wrap them in braces. 192 | v = partial.length === 0 ? "{}": gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}": "{" + partial.join(",") + "}"; 193 | gap = mind; 194 | return v; 195 | } 196 | } 197 | 198 | // If the JSON object does not yet have a stringify method, give it one. 199 | if (typeof JSON.stringify !== "function") { 200 | meta = { // table of character substitutions 201 | "\b": "\\b", 202 | "\t": "\\t", 203 | "\n": "\\n", 204 | "\f": "\\f", 205 | "\r": "\\r", 206 | "\"": "\\\"", 207 | "\\": "\\\\" 208 | }; 209 | JSON.stringify = function(value, replacer, space) { 210 | 211 | // The stringify method takes a value and an optional replacer, and an optional 212 | // space parameter, and returns a JSON text. The replacer can be a function 213 | // that can replace values, or an array of strings that will select the keys. 214 | // A default replacer method can be provided. Use of the space parameter can 215 | // produce text that is more easily readable. 216 | var i; 217 | gap = ""; 218 | indent = ""; 219 | 220 | // If the space parameter is a number, make an indent string containing that 221 | // many spaces. 222 | if (typeof space === "number") { 223 | for (i = 0; i < space; i += 1) { 224 | indent += " "; 225 | } 226 | 227 | // If the space parameter is a string, it will be used as the indent string. 228 | } else if (typeof space === "string") { 229 | indent = space; 230 | } 231 | 232 | // If there is a replacer, it must be a function or an array. 233 | // Otherwise, throw an error. 234 | rep = replacer; 235 | if (replacer && typeof replacer !== "function" && (typeof replacer !== "object" || typeof replacer.length !== "number")) { 236 | throw new Error("JSON.stringify"); 237 | } 238 | 239 | // Initialize the reference keeper. 240 | seen = []; 241 | 242 | // Make a fake root object containing our value under the key of "". 243 | // Return the result of stringifying the value. 244 | return str("", { 245 | "": value 246 | }); 247 | }; 248 | } 249 | 250 | // If the JSON object does not yet have a parse method, give it one. 251 | if (typeof JSON.parse !== "function") { 252 | JSON.parse = function(text, reviver) { 253 | 254 | // The parse method takes a text and an optional reviver function, and returns 255 | // a JavaScript value if the text is a valid JSON text. 256 | var j; 257 | 258 | function walk(holder, key) { 259 | 260 | // The walk method is used to recursively walk the resulting structure so 261 | // that modifications can be made. 262 | var k; 263 | var v; 264 | var value = holder[key]; 265 | if (value && typeof value === "object") { 266 | for (k in value) { 267 | if (Object.prototype.hasOwnProperty.call(value, k)) { 268 | v = walk(value, k); 269 | if (v !== undefined) { 270 | value[k] = v; 271 | } else { 272 | delete value[k]; 273 | } 274 | } 275 | } 276 | } 277 | return reviver.call(holder, key, value); 278 | } 279 | 280 | // Parsing happens in four stages. In the first stage, we replace certain 281 | // Unicode characters with escape sequences. JavaScript handles many characters 282 | // incorrectly, either silently deleting them, or treating them as line endings. 283 | text = String(text); 284 | rx_dangerous.lastIndex = 0; 285 | if (rx_dangerous.test(text)) { 286 | text = text.replace(rx_dangerous, 287 | function(a) { 288 | return ("\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice( - 4)); 289 | }); 290 | } 291 | 292 | // In the second stage, we run the text against regular expressions that look 293 | // for non-JSON patterns. We are especially concerned with "()" and "new" 294 | // because they can cause invocation, and "=" because it can cause mutation. 295 | // But just to be safe, we want to reject all unexpected forms. 296 | // We split the second stage into 4 regexp operations in order to work around 297 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 298 | // replace the JSON backslash pairs with "@" (a non-JSON character). Second, we 299 | // replace all simple value tokens with "]" characters. Third, we delete all 300 | // open brackets that follow a colon or comma or that begin the text. Finally, 301 | // we look to see that the remaining characters are only whitespace or "]" or 302 | // "," or ":" or "{" or "}". If that is so, then the text is safe for eval. 303 | if (rx_one.test(text.replace(rx_two, "@").replace(rx_three, "]").replace(rx_four, ""))) { 304 | 305 | // In the third stage we use the eval function to compile the text into a 306 | // JavaScript structure. The "{" operator is subject to a syntactic ambiguity 307 | // in JavaScript: it can begin a block or an object literal. We wrap the text 308 | // in parens to eliminate the ambiguity. 309 | j = eval("(" + text + ")"); 310 | 311 | // In the optional fourth stage, we recursively walk the new structure, passing 312 | // each name/value pair to a reviver function for possible transformation. 313 | return (typeof reviver === "function") ? walk({ 314 | "": j 315 | }, 316 | "") : j; 317 | } 318 | 319 | // If the text is not JSON parseable, then a SyntaxError is thrown. 320 | throw new SyntaxError("JSON.parse"); 321 | }; 322 | } 323 | } ()); 324 | 325 | // JSON 定义结束 326 | // ============ 327 | 328 | var scriptName = "Auto Color Chart"; 329 | var version = "2.23 beta"; 330 | 331 | // UI 开始 332 | // ========== 333 | var panelGlobal = this; 334 | var palette = (function() { 335 | var myPanel = (panelGlobal instanceof Panel) ? panelGlobal: new Window("palette", scriptName + " v" + version, undefined, { 336 | resizeable: true, 337 | closeButton: true 338 | }); 339 | { 340 | // EXTRACTTAB 341 | // ========== 342 | var extractTab = myPanel.add("tabbedpanel", undefined, undefined, { 343 | name: "extractTab" 344 | }); 345 | extractTab.alignChildren = "fill"; 346 | extractTab.preferredSize.width = 336.797; 347 | extractTab.margins = 0; 348 | 349 | // EXTRACTTABTEXT 350 | // ============== 351 | var extractTabText = extractTab.add("tab", undefined, undefined, { 352 | name: "extractTabText" 353 | }); 354 | extractTabText.text = "从颜色组"; 355 | extractTabText.orientation = "column"; 356 | extractTabText.alignChildren = ["center", "top"]; 357 | extractTabText.spacing = 10; 358 | extractTabText.margins = 10; 359 | 360 | var ColorNameGroup = extractTabText.add("group", undefined, { 361 | name: "ColorNameGroup" 362 | }); 363 | ColorNameGroup.orientation = "row"; 364 | ColorNameGroup.alignChildren = ["left", "top"]; 365 | ColorNameGroup.spacing = 10; 366 | ColorNameGroup.margins = 0; 367 | ColorNameGroup.alignment = ["left", "top"]; 368 | 369 | var colorChartImageTxt = ColorNameGroup.add("statictext", undefined, undefined, { 370 | name: "colorChartImageTxt" 371 | }); 372 | colorChartImageTxt.text = "色见本名称"; 373 | colorChartImageTxt.preferredSize.width = 100; 374 | colorChartImageTxt.alignment = ["left", "top"]; 375 | 376 | var Color1Disp = ColorNameGroup.add("button", undefined, undefined, { 377 | name: "Color1Disp" 378 | }); 379 | Color1Disp.size = [15, 15]; 380 | 381 | var Color2Disp = ColorNameGroup.add("button", undefined, undefined, { 382 | name: "Color2Disp" 383 | }); 384 | Color2Disp.size = [15, 15]; 385 | 386 | var Color3Disp = ColorNameGroup.add("button", undefined, undefined, { 387 | name: "Color3Disp" 388 | }); 389 | Color3Disp.size = [15, 15]; 390 | 391 | 392 | var Color4Disp = ColorNameGroup.add("button", undefined, undefined, { 393 | name: "Color4Disp" 394 | }); 395 | Color4Disp.size = [15, 15]; 396 | 397 | var Color5Disp = ColorNameGroup.add("button", undefined, undefined, { 398 | name: "Color5Disp" 399 | }); 400 | Color5Disp.size = [15, 15]; 401 | 402 | var Color6Disp = ColorNameGroup.add("button", undefined, undefined, { 403 | name: "Color6Disp" 404 | }); 405 | Color6Disp.size = [15, 15]; 406 | 407 | var Color7Disp = ColorNameGroup.add("button", undefined, undefined, { 408 | name: "Color7Disp" 409 | }); 410 | Color7Disp.size = [15, 15]; 411 | 412 | var Color8Disp = ColorNameGroup.add("button", undefined, undefined, { 413 | name: "Color8Disp" 414 | }); 415 | Color8Disp.size = [15, 15]; 416 | 417 | var Color9Disp = ColorNameGroup.add("button", undefined, undefined, { 418 | name: "Color9Disp" 419 | }); 420 | Color9Disp.size = [15, 15]; 421 | 422 | var Color10Disp = ColorNameGroup.add("button", undefined, undefined, { 423 | name: "Color10Disp" 424 | }); 425 | Color10Disp.size = [15, 15]; 426 | 427 | // EXTRACTGRUOP 428 | // ============ 429 | var extractGruop = extractTabText.add("group", undefined, { 430 | name: "extractGruop" 431 | }); 432 | extractGruop.orientation = "row"; 433 | extractGruop.alignChildren = ["left", "center"]; 434 | extractGruop.spacing = 10; 435 | extractGruop.margins = 0; 436 | 437 | var colorSlect_array = []; 438 | var colorSlect = extractGruop.add("listbox", undefined, undefined, { 439 | name: "colorSlect", 440 | items: colorSlect_array, 441 | multiselect: false, 442 | numberOfColumns: 2, 443 | columnTitles: ['序号', '颜色'], 444 | showHeaders: true 445 | }); 446 | colorSlect.preferredSize.width = 200; 447 | colorSlect.preferredSize.height = 200; 448 | 449 | // BTNGROUP 450 | // ======== 451 | var btnGroup = extractGruop.add("group", undefined, { 452 | name: "btnGroup" 453 | }); 454 | btnGroup.orientation = "column"; 455 | btnGroup.alignChildren = ["center", "top"]; 456 | btnGroup.spacing = 10; 457 | btnGroup.margins = 0; 458 | btnGroup.alignment = ["left", "top"]; 459 | 460 | var extractGruopAllBtn = btnGroup.add("button", undefined, undefined, { 461 | name: "extractAllBtn" 462 | }); 463 | extractGruopAllBtn.text = "提取组全部"; 464 | extractGruopAllBtn.preferredSize.width = 101; 465 | extractGruopAllBtn.preferredSize.height = 40; 466 | extractGruopAllBtn.enabled = false; 467 | 468 | 469 | var colorPickerBtn = btnGroup.add("button", undefined, undefined, { 470 | name: "BtcolorPickerBtnn0" 471 | }); 472 | colorPickerBtn.text = "颜色拾取器"; 473 | colorPickerBtn.preferredSize.width = 101; 474 | colorPickerBtn.preferredSize.height = 27; 475 | colorPickerBtn.enabled = false; 476 | 477 | var pickedColorDisp = btnGroup.add("button", undefined, undefined, { 478 | name: "Btn1" 479 | }); 480 | pickedColorDisp.preferredSize.width = 101; 481 | pickedColorDisp.preferredSize.height = 5; 482 | //pickedColor.enabled = false; 483 | 484 | var Btn2 = btnGroup.add("button", undefined, undefined, { 485 | name: "Btn2" 486 | }); 487 | Btn2.preferredSize.width = 101; 488 | Btn2.preferredSize.height = 21; 489 | Btn2.enabled = false; 490 | Btn2.visible = false; 491 | 492 | 493 | var openCategroyBtn = btnGroup.add("button", undefined, undefined, { 494 | name: "openCategroyBtn" 495 | }); 496 | openCategroyBtn.text = "打开类型面板"; 497 | openCategroyBtn.preferredSize.width = 101; 498 | openCategroyBtn.enabled = false; 499 | 500 | 501 | var openFileBtn = btnGroup.add("button", undefined, undefined, { 502 | name: "openFileBtn" 503 | }); 504 | openFileBtn.text = "打开色见数据"; 505 | openFileBtn.preferredSize.width = 101; 506 | 507 | /* 508 | // COLORPICKERTAB 509 | // =========== 510 | var colorPickerTab = extractTab.add("tab", undefined, undefined, { 511 | name: "colorPickerTab" 512 | }); 513 | colorPickerTab.text = "吸色"; 514 | colorPickerTab.orientation = "column"; 515 | colorPickerTab.alignChildren = ["center", "center"]; 516 | colorPickerTab.spacing = 10; 517 | colorPickerTab.margins = 10; 518 | var colorPickerBtn = colorPickerTab.add("button", undefined, undefined, { 519 | name: "colorPickerBtn" 520 | }); 521 | colorPickerBtn.text = "颜色拾取器"; 522 | colorPickerBtn.preferredSize.width = 105; 523 | colorPickerBtn.preferredSize.height = 40; 524 | var msgText = colorPickerTab.add("edittext", undefined, undefined, { 525 | name: "msgText" 526 | }); 527 | msgText.preferredSize.width = 110; 528 | msgText.text = "R: G: B: "; 529 | 530 | 531 | // CONFIGTAB 532 | // ========= 533 | var configTab = extractTab.add("tab", undefined, undefined, { 534 | name: "configTab" 535 | }); 536 | configTab.text = "配置"; 537 | configTab.orientation = "row"; 538 | configTab.alignChildren = ["left", "top"]; 539 | configTab.spacing = 10; 540 | configTab.margins = 10; 541 | */ 542 | } 543 | //全局变量 544 | //=========== 545 | var colorChartJSON = {}; 546 | var myWindow; 547 | const myBlack = [0, 0, 0]; 548 | 549 | // 旧版JSON映射表 550 | const keyMappings = { 551 | "hi": "高光", 552 | "normal": "正常", 553 | "shadow": "阴影", 554 | "2nd_shadow": "2号影", 555 | "hi_in_shadow": "影中高光", 556 | "hi_in_2nd_shadow": "2号影中高光", 557 | "shadow_s_hi": "阴影的高光", 558 | "2nd_shadow_s_hi": "2号影的高光", 559 | "2nd_shadow_hi": "2号影的高光", 560 | "normal_tp": "TP线" 561 | }; 562 | 563 | var userSelection = { 564 | extractColorMode: "仅从选中的组" 565 | }; 566 | 567 | const colorDisplays = [Color1Disp, Color2Disp, Color3Disp, 568 | Color4Disp, Color5Disp, Color6Disp, 569 | Color7Disp, Color8Disp, Color9Disp, 570 | Color10Disp]; 571 | 572 | // 获取配置文件夹(暂未使用) 573 | // ======================= 574 | function getConfigFolder() { 575 | var userDataFolder = Folder.userData; 576 | var aescriptsFolder = Folder(userDataFolder.toString() + "/AutoColorChart/Config"); 577 | if (!aescriptsFolder.exists) { 578 | var checkFolder = aescriptsFolder.create(); 579 | if (!checkFolder) { 580 | alert("未能创建配置文件!",scriptName); 581 | aescriptsFolder = Folder.temp; 582 | } 583 | } 584 | return aescriptsFolder.toString(); 585 | } 586 | 587 | var configFolder = getConfigFolder() 588 | var config = {}; 589 | 590 | // 更新配置(暂未使用) 591 | // ======================= 592 | function updateConfig() { 593 | var configFolder = getConfigFolder() 594 | var file = File(configFolder + "/config.json"); 595 | // 检查文件是否存在且不为空 596 | if (file.exists && file.length > 0) { 597 | // 打开文件以读取模式 598 | if (file.open("r")) { 599 | // 读取文件内容 600 | var content = file.read(); 601 | // 将文件内容解析为一个对象 602 | var parsedConfig = JSON.parse(content); 603 | 604 | // 关闭文件 605 | file.close(); 606 | 607 | // 更新config对象的值 608 | config["colors"] = parsedConfig["colors"]; 609 | config["override_colors"] = parsedConfig["override_colors"]; 610 | config["distance"] = parsedConfig["distance"]; 611 | config["image_path"] = parsedConfig["image_path"]; 612 | config["save_path"] = parsedConfig["save_path"]; 613 | 614 | } else { 615 | alert("无法打开文件:" + file.fsName, scriptName); 616 | } 617 | } 618 | 619 | } 620 | 621 | 622 | // 颜色显示器按钮 623 | // ============ 624 | function setColorDisp(disp, color, visible) { 625 | function customDraw() { 626 | with(this) { 627 | graphics.drawOSControl(); 628 | graphics.rectPath(0, 0, size[0], size[1]); 629 | graphics.fillPath(fillBrush); 630 | } 631 | } 632 | disp.fillBrush = disp.graphics.newBrush(disp.graphics.BrushType.SOLID_COLOR, color); 633 | disp.onDraw = customDraw; 634 | disp.enabled = false; 635 | disp.enabled = true; 636 | disp.enabled = visible; 637 | disp.visible = visible; 638 | } 639 | 640 | // 初始化颜色显示器 641 | // =============== 642 | function initColorDisp(allDisp){ 643 | for (var i = 0; i < colorDisplays.length; i++) { 644 | setColorDisp(colorDisplays[i], myBlack, false); 645 | } 646 | if (allDisp) setColorDisp(pickedColorDisp, myBlack, false); 647 | } 648 | 649 | initColorDisp(1); 650 | 651 | // 函数 652 | // ========= 653 | // 调用AE颜色拾取器 654 | // =============== 655 | function colorPicker(startValue) { 656 | // 活动合成 657 | if (!app.project.activeItem || !(app.project.activeItem instanceof CompItem)) { 658 | alert("请选择一个合成!", scriptName); 659 | return []; 660 | } 661 | 662 | // 临时图层 663 | var tempLayer = app.project.activeItem.layers.addShape(); 664 | var newColorControl = tempLayer("ADBE Effect Parade").addProperty("ADBE Color Control"); 665 | var theColorProp = newColorControl("ADBE Color Control-0001"); 666 | 667 | // s初始值 668 | if (startValue && startValue.length == 3) { 669 | theColorProp.setValue(startValue); 670 | } 671 | 672 | // 执行 673 | var editValueID = 2240 // or app.findMenuCommandId("Edit Value..."); 674 | theColorProp.selected = true; 675 | app.executeCommand(editValueID); 676 | 677 | // 获取结果 678 | var result = theColorProp.value; 679 | 680 | // 删除临时层 681 | if (tempLayer) { 682 | tempLayer.remove(); 683 | } 684 | return result; 685 | } 686 | 687 | // RGB转HEX 688 | // ================= 689 | function rgbToHex(rgb) { 690 | var hex = []; 691 | for (var i = 0; i < 3; i++) { 692 | var val = Math.round(rgb[i] * 255); 693 | var hexVal = val.toString(16); 694 | if (hexVal.length < 2) { 695 | hexVal = "0" + hexVal; 696 | } 697 | hex.push(hexVal.toUpperCase()); 698 | } 699 | return hex.join(""); 700 | } 701 | 702 | // 查找JSON中指定颜色 703 | // ================= 704 | function findColorInJson(parsedJson, color) { 705 | var myColorStr = JSON.stringify(color); 706 | 707 | for (var imageKey in parsedJson) { 708 | var colorGroups = parsedJson[imageKey]; 709 | 710 | for (var groupKey in colorGroups) { 711 | var colors = colorGroups[groupKey]; 712 | 713 | for (var colorKey in colors) { 714 | var colorArray = colors[colorKey]; 715 | 716 | if (JSON.stringify(colorArray) === myColorStr) { 717 | return groupKey; 718 | } 719 | } 720 | } 721 | } 722 | 723 | return null; 724 | } 725 | 726 | // 字符串颜色数组 727 | // ============= 728 | function parseColorStr(colorStr) { 729 | var colorArray = colorStr.slice(1, -1).split(','); 730 | for (var i = 0; i < colorArray.length; i++) { 731 | colorArray[i] = +colorArray[i] / 255; 732 | } 733 | return colorArray; 734 | } 735 | 736 | // 创建按颜色类型去色窗口 737 | // ==================== 738 | function createButtonsFromJson(jsonData) { 739 | var myWindow = new Window("palette", "按类型取颜色", undefined); 740 | 741 | for (var imageName in jsonData) { 742 | var imageGroup = myWindow.add("group", undefined, imageName); 743 | imageGroup.orientation = "column"; 744 | imageGroup.alignChildren = ["left", "center"] 745 | imageGroup.add("statictext", undefined, "提取色见 " + imageName + " 中的颜色"); 746 | // 添加单选按钮组 747 | var radioGroup = imageGroup.add("group", undefined); 748 | radioGroup.orientation = "row"; 749 | radioGroup.add("statictext", undefined, "提取模式:"); 750 | var allColorsRadio = radioGroup.add("radiobutton", undefined, "色见全部"); 751 | var singleColorRadio = radioGroup.add("radiobutton", undefined, "仅从选中的组"); 752 | singleColorRadio.value = true; // 默认选择"仅从选中的组" 753 | 754 | allColorsRadio.onClick = function() { userSelection.extractColorMode = "色见全部"; } 755 | singleColorRadio.onClick = function() { userSelection.extractColorMode = "仅从选中的组"; } 756 | 757 | 758 | var addedKeys = {}; // 用于跟踪已添加的键 759 | var buttonRow; // 当前行 760 | var buttonCount = 0; // 当前行的按钮计数 761 | 762 | for (var numberKey in jsonData[imageName]) { 763 | for (var itemKey in jsonData[imageName][numberKey]) { 764 | if (!addedKeys[itemKey]) { 765 | // 每3个按钮开始一个新的行 766 | if (buttonCount % 3 === 0) { 767 | buttonRow = imageGroup.add("group", undefined, {orientation: "row"}); 768 | 769 | } 770 | var buttonText = keyMappings[itemKey] || itemKey; // 如果没有找到映射,则使用原始键值 771 | var btn = buttonRow.add("button", undefined, buttonText); 772 | btn.preferredSize.width = 80; 773 | btn.preferredSize.height = 25; 774 | btn.onClick = function(key) { 775 | return function() { 776 | if(userSelection.extractColorMode === "仅从选中的组") extractOneColor(key); 777 | else if(userSelection.extractColorMode === "色见全部") extractAllColor(key); 778 | 779 | }; 780 | }(itemKey); 781 | addedKeys[itemKey] = true; 782 | buttonCount++; 783 | } 784 | } 785 | } 786 | } 787 | 788 | myWindow.center(); 789 | myWindow.show(); 790 | return myWindow; 791 | 792 | } 793 | 794 | // 提取选择组中单一指定颜色 795 | // =============== 796 | function extractOneColor(colorType) { 797 | if(!app.project.activeItem) {alert("请打开一个合成", scriptName); return;} 798 | var selectedLayers = app.project.activeItem.selectedLayers; 799 | var newLayer; 800 | if (selectedLayers.length > 0) { 801 | var selectedItems = colorSlect.selection; 802 | if (!selectedItems) { 803 | alert("请在列表中选择一项.", scriptName); 804 | return; 805 | } 806 | 807 | if (! (selectedItems instanceof Array)) { 808 | selectedItems = [selectedItems]; 809 | } 810 | 811 | for (var i = 0; i < selectedLayers.length; i++) { 812 | var originalLayer = selectedLayers[i]; 813 | newLayer = originalLayer.duplicate(); 814 | var colorEffect = newLayer.property("Effects").addProperty("Color Keep"); 815 | 816 | for (var j = 0; j < selectedItems.length; j++) { 817 | var selectedColorText = selectedItems[j].subItems[0].text; 818 | var colorParts = selectedColorText.split(";"); 819 | 820 | var color; 821 | 822 | for (var k = 0; k < colorParts.length; k++) { 823 | var parts = colorParts[k].split(":"); 824 | if (parts[0] === colorType) { 825 | color = parts[1]; 826 | try { 827 | colorEffect.property(1).setValue(1); 828 | colorEffect.property(2).setValue(parseColorStr(color)); 829 | } catch(e) { 830 | alert("提取颜色时出错: " + e, scriptName); 831 | } 832 | } 833 | } 834 | 835 | } 836 | } 837 | } else { 838 | alert("请至少选择一个图层!", scriptName); 839 | } 840 | return newLayer 841 | } 842 | 843 | // 提取色见全部指定颜色 844 | // =============== 845 | function extractAllColor(colorType) { 846 | var selectedLayers = app.project.activeItem.selectedLayers; 847 | var newLayer; 848 | if (selectedLayers.length > 0) { 849 | try { 850 | var resultArray = []; 851 | for (var key in colorChartJSON) { 852 | var innerObj = colorChartJSON[key]; 853 | for (var innerKey in innerObj) { 854 | if (innerObj[innerKey].hasOwnProperty(colorType)) { 855 | var colorArray = innerObj[innerKey][colorType]; 856 | for (var i = 0; i < colorArray.length; i++) { 857 | colorArray[i] = colorArray[i] / 255; 858 | } 859 | resultArray.push(colorArray); 860 | } 861 | } 862 | } 863 | 864 | 865 | for (var j = 0; j < selectedLayers.length; j++) { 866 | var originalLayer = selectedLayers[j]; 867 | newLayer = originalLayer.duplicate(); 868 | var colorEffect = newLayer.property("Effects").addProperty("Color Keep"); 869 | for (var i = 0; i < resultArray.length; i++) { 870 | colorEffect.property(1).setValue(resultArray.length); 871 | colorEffect.property(i + 2).setValue(resultArray[i]); 872 | } 873 | } 874 | 875 | } catch(e) { 876 | alert(e); 877 | } 878 | } else alert("请至少选择一个图层!", scriptName); 879 | return newLayer; 880 | } 881 | // 转换新JSON 882 | // =========== 883 | function convertNewJSON(newJson) { 884 | // 新的JSON对象 885 | var oldJSON = {}; 886 | 887 | // 从原始JSON获取图像名称,并转换为.tga格式 888 | var imageName = newJson.colorChartData.image.name; 889 | var fileName = imageName; 890 | 891 | // 初始化文件名键值 892 | oldJSON[fileName] = {}; 893 | 894 | // 获取所有组 895 | var groups = newJson.colorChartData.image.group; 896 | var groupCount = groups.length; 897 | 898 | // 遍历所有组 899 | for (var i = 0; i < groupCount; i++) { 900 | var group = groups[i]; 901 | var groupId = group.id.toString(); 902 | 903 | oldJSON[fileName][groupId] = {}; 904 | 905 | // 检查color数据是单个对象还是数组 906 | var colorData = group.color; 907 | var isColorArray = Object.prototype.toString.call(colorData) === '[object Array]'; 908 | 909 | if (group.validBoxNum === 1 && !isColorArray) { 910 | // 如果是单个颜色对象 911 | var color = colorData; 912 | oldJSON[fileName][groupId][color.colorType] = color.RGB; 913 | } else if (group.validBoxNum && isColorArray && colorData.length === group.validBoxNum) { 914 | // 如果是颜色数组 915 | for (var j = 0; j < colorData.length; j++) { 916 | var color = colorData[j]; 917 | oldJSON[fileName][groupId][color.colorType] = color.RGB; 918 | } 919 | } 920 | } 921 | 922 | return oldJSON; 923 | } 924 | // 解析 XML 返回 JSON 925 | // ================= 926 | function parseXMLtoJSON(xmlData) { 927 | var jsonData = {}; 928 | var imageList = xmlData.image; 929 | for (var i = 0; i < imageList.length(); i++) { 930 | var image = imageList[i]; 931 | var imageName = image.@name.toString(); 932 | jsonData[imageName] = {}; 933 | 934 | var groupList = image.group; 935 | for (var j = 0; j < groupList.length(); j++) { 936 | var group = groupList[j]; 937 | var groupId = group.@id.toString(); 938 | jsonData[imageName][groupId] = {}; 939 | 940 | var colorList = group.color; 941 | for (var k = 0; k < colorList.length(); k++) { 942 | var color = colorList[k]; 943 | var colorType = color.@colorType.toString(); 944 | jsonData[imageName][groupId][colorType] = [ 945 | parseInt(color.@r.toString()), 946 | parseInt(color.@g.toString()), 947 | parseInt(color.@b.toString()) 948 | ]; 949 | } 950 | } 951 | } 952 | return jsonData; 953 | } 954 | 955 | // 颜色拾取器单击事件 956 | // ================= 957 | colorPickerBtn.onClick = function() { 958 | 959 | var myColor = colorPicker([1, 0, 0]); 960 | // var color1 = "R: " + Math.round(myColor[0] * 255).toString() + " G: " + Math.round(myColor[1] * 255).toString() + " B: " + Math.round(myColor[2] * 255).toString() + "\n"; 961 | // msgText.text = String(color1); 962 | var myColorN = [Math.round(myColor[0] * 255), Math.round(myColor[1] * 255), Math.round(myColor[2] * 255)]; 963 | setColorDisp(pickedColorDisp, myColor, true); 964 | 965 | try { 966 | var groupNumber = findColorInJson(colorChartJSON, myColorN); 967 | if (groupNumber != null) { 968 | colorSlect.selection = null; 969 | colorSlect.selection = colorSlect.find(groupNumber.toString()); 970 | 971 | } else { 972 | alert("未找到指定颜色,请检查色见数据内容。", scriptName); 973 | } 974 | 975 | } catch(e) { 976 | alert(e, scriptName); 977 | } 978 | } 979 | 980 | // 列表选择事件 981 | // =========== 982 | colorSlect.onChange = function() { 983 | function parseColor(color) { 984 | var colorArray = color.slice(1, -1).split(','); 985 | for (var i = 0; i < colorArray.length; i++) { 986 | colorArray[i] = +colorArray[i] / 255; 987 | } 988 | return colorArray; 989 | } 990 | 991 | var selectedItems = colorSlect.selection; 992 | if (!selectedItems) return; 993 | if (!(selectedItems instanceof Array)) { 994 | selectedItems = [selectedItems]; 995 | } 996 | 997 | var selectedColorText = selectedItems[0].subItems[0].text; 998 | var colorParts = selectedColorText.split(";"); 999 | 1000 | // 重置所有颜色显示器 1001 | initColorDisp(0); 1002 | 1003 | // 设置新的颜色 1004 | for (var i = 0; i < colorParts.length && i < colorDisplays.length; i++) { 1005 | var parts = colorParts[i].split(":"); 1006 | var color = parseColor(parts[1]); 1007 | setColorDisp(colorDisplays[i], color, true); 1008 | } 1009 | } 1010 | 1011 | // 打开按钮单击事件 1012 | // =============== 1013 | openFileBtn.onClick = function() { 1014 | if(typeof myWindow == 'object') myWindow.close() 1015 | var file = File.openDialog("请打开色见数据文件", "JSON Files:*.json;XML Files:*.xml"); 1016 | if (file !== null) { 1017 | var fileNameArray = file.name.toLowerCase().split('.'); 1018 | var fileExtension = fileNameArray[fileNameArray.length - 1]; 1019 | 1020 | file.open('r'); 1021 | var content = file.read(); 1022 | file.close(); 1023 | 1024 | if (fileExtension === 'json') { 1025 | // 解析 JSON 文件 1026 | colorChartJSON = JSON.parse(content); 1027 | // 检查是否为新版JSON 1028 | if(colorChartJSON.colorChartData !== undefined) 1029 | colorChartJSON = convertNewJSON(colorChartJSON); 1030 | // 启用相应的按钮 1031 | openCategroyBtn.enabled = true; 1032 | colorPickerBtn.enabled = true; 1033 | extractGruopAllBtn.enabled = true; 1034 | } else if (fileExtension === 'xml') { 1035 | // 解析 XML 文件 1036 | var xmlData = new XML(content); 1037 | colorChartJSON = parseXMLtoJSON(xmlData); 1038 | // 启用相应的按钮 1039 | openCategroyBtn.enabled = true; 1040 | colorPickerBtn.enabled = true; 1041 | extractGruopAllBtn.enabled = true; 1042 | } else { 1043 | alert('无效的文件类型。请选择 JSON 或 XML 文件。', scriptName); 1044 | } 1045 | } 1046 | myWindow = createButtonsFromJson(colorChartJSON); 1047 | // 更新 colorChartImageTxt.text 1048 | for (var key in colorChartJSON) { 1049 | colorChartImageTxt.text = key; 1050 | imputImage = key; 1051 | break; // 只取第一个 key 图片名称 1052 | } 1053 | 1054 | // 更新 listbox 1055 | colorSlect.removeAll(); // 清除原有列表 1056 | for (var key in colorChartJSON) { 1057 | var colorInfo = colorChartJSON[key]; 1058 | for (var group in colorInfo) { 1059 | var colors = colorInfo[group]; 1060 | var colorStr = ""; 1061 | for (var colorName in colors) { 1062 | var colorItem = colors[colorName]; 1063 | //colorName = colorName.charAt(0).toUpperCase() + colorName.slice(1); // 将首字母大写 1064 | colorStr += colorName + ':[' + colorItem.toString() + '];'; 1065 | } 1066 | var item = colorSlect.add('item', group); 1067 | item.subItems[0].text = colorStr; // 'Color' 列 1068 | } 1069 | } 1070 | }; 1071 | 1072 | openCategroyBtn.onClick = function(){ 1073 | createButtonsFromJson(colorChartJSON); 1074 | } 1075 | 1076 | // 提取全部组按钮单击事件 1077 | // ===================== 1078 | extractGruopAllBtn.onClick = function() { 1079 | var selectedLayers = app.project.activeItem.selectedLayers; 1080 | if (selectedLayers.length > 0) { 1081 | for (var i = 0; i < selectedLayers.length; i++) { 1082 | var originalLayer = selectedLayers[i]; 1083 | var selectedItems = colorSlect.selection; 1084 | 1085 | if (!selectedItems) { 1086 | alert("请在列表中选择一项。", scriptName); 1087 | return; 1088 | } 1089 | 1090 | if (!(selectedItems instanceof Array)) { 1091 | selectedItems = [selectedItems]; 1092 | } 1093 | 1094 | var newLayer = originalLayer.duplicate(); 1095 | var colorEffect = newLayer.property("Effects").addProperty("Color Keep"); 1096 | 1097 | for (var j = 0; j < selectedItems.length; j++) { 1098 | var selectedItem = selectedItems[j]; 1099 | if (!selectedItem || !selectedItem.subItems || selectedItem.subItems.length === 0) { 1100 | continue; // 跳过无效的选项 1101 | } 1102 | 1103 | var selectedColorText = selectedItem.subItems[0].text; 1104 | var colorParts = selectedColorText.split(";"); 1105 | 1106 | try { 1107 | colorEffect.property(1).setValue(colorParts.length - 1); 1108 | 1109 | for (var k = 0; k < colorParts.length; k++) { 1110 | var parts = colorParts[k].split(":"); 1111 | if (parts.length < 2) { 1112 | continue; // 跳过无效的颜色部分 1113 | } 1114 | var colorArray = parts[1].slice(1, -1).split(','); 1115 | 1116 | for (var l = 0; l < colorArray.length; l++) { 1117 | colorArray[l] = +colorArray[l] / 255; 1118 | } 1119 | colorEffect.property(k + 2).setValue(colorArray); 1120 | } 1121 | 1122 | } catch(e) { 1123 | alert(e); 1124 | } 1125 | } 1126 | } 1127 | } else { 1128 | alert("请选择至少一个图层!", scriptName); 1129 | } 1130 | } 1131 | 1132 | // UI 结束 1133 | // ========== 1134 | myPanel.layout.layout(true); 1135 | myPanel.layout.resize(); 1136 | myPanel.onResizing = myPanel.onResize = function() { 1137 | this.layout.resize() 1138 | } 1139 | if (myPanel instanceof Window) myPanel.show(); 1140 | return myPanel; 1141 | } ()) 1142 | -------------------------------------------------------------------------------- /AEScripts/AutoColorChartBeta_JP.jsx: -------------------------------------------------------------------------------- 1 | //===========================================================================================// 2 | // Script Name: AutoColorChart.jsx 3 | // Version: 2.23 beta 4 | // Author: 千石まよひ 5 | // Last Update: 2024/05/29 6 | // License: MIT 7 | // Copyright 2024 SengokuMayoi (Ma Chenxing) 8 | //============================================================================================// 9 | // 为ES3系统带来JSON对象支持,包含 JSON.parse() 和 JSON.stringify() 方法 10 | // =================================================================== 11 | 12 | if (typeof JSON !== "object") { 13 | JSON = {}; 14 | } 15 | 16 | (function() { 17 | "use strict"; 18 | 19 | var rx_one = /^[\],:{}\s]*$/; 20 | var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; 21 | var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; 22 | var rx_four = /(?:^|:|,)(?:\s*\[)+/g; 23 | var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 24 | var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; 25 | 26 | function f(n) { 27 | // Format integers to have at least two digits. 28 | return (n < 10) ? "0" + n: n; 29 | } 30 | 31 | function this_value() { 32 | return this.valueOf(); 33 | } 34 | 35 | if (typeof Date.prototype.toJSON !== "function") { 36 | 37 | Date.prototype.toJSON = function() { 38 | 39 | return isFinite(this.valueOf()) ? (this.getUTCFullYear() + "-" + f(this.getUTCMonth() + 1) + "-" + f(this.getUTCDate()) + "T" + f(this.getUTCHours()) + ":" + f(this.getUTCMinutes()) + ":" + f(this.getUTCSeconds()) + "Z") : null; 40 | }; 41 | 42 | Boolean.prototype.toJSON = this_value; 43 | Number.prototype.toJSON = this_value; 44 | String.prototype.toJSON = this_value; 45 | } 46 | 47 | var gap; 48 | var indent; 49 | var meta; 50 | var rep; 51 | 52 | function quote(string) { 53 | 54 | // If the string contains no control characters, no quote characters, and no 55 | // backslash characters, then we can safely slap some quotes around it. 56 | // Otherwise we must also replace the offending characters with safe escape 57 | // sequences. 58 | rx_escapable.lastIndex = 0; 59 | return rx_escapable.test(string) ? "\"" + string.replace(rx_escapable, 60 | function(a) { 61 | var c = meta[a]; 62 | return typeof c === "string" ? c: "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice( - 4); 63 | }) + "\"": "\"" + string + "\""; 64 | } 65 | 66 | // This variable is initialized with an empty array every time 67 | // JSON.stringify() is invoked and checked by the str() function. It's 68 | // used to keep references to object structures and capture cyclic 69 | // objects. Every new object is checked for its existence in this 70 | // array. If it's found it means the JSON object is cyclic and we have 71 | // to stop execution and throw a TypeError accordingly the ECMA262 72 | // (see NOTE 1 by the link https://tc39.es/ecma262/#sec-json.stringify). 73 | var seen; 74 | 75 | // Emulate [].includes(). It's actual for old-fashioned JScript. 76 | function includes(array, value) { 77 | var i; 78 | for (i = 0; i < array.length; i += 1) { 79 | if (value === array[i]) { 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | function str(key, holder) { 87 | 88 | // Produce a string from holder[key]. 89 | var i; // The loop counter. 90 | var k; // The member key. 91 | var v; // The member value. 92 | var length; 93 | var mind = gap; 94 | var partial; 95 | var value = holder[key]; 96 | 97 | // If the value has a toJSON method, call it to obtain a replacement value. 98 | if (value && typeof value === "object" && typeof value.toJSON === "function") { 99 | value = value.toJSON(key); 100 | } 101 | 102 | // If we were called with a replacer function, then call the replacer to 103 | // obtain a replacement value. 104 | if (typeof rep === "function") { 105 | value = rep.call(holder, key, value); 106 | } 107 | 108 | // What happens next depends on the value's type. 109 | switch (typeof value) { 110 | case "string": 111 | return quote(value); 112 | 113 | case "number": 114 | 115 | // JSON numbers must be finite. Encode non-finite numbers as null. 116 | return (isFinite(value)) ? String(value) : "null"; 117 | 118 | case "boolean": 119 | case "null": 120 | 121 | // If the value is a boolean or null, convert it to a string. Note: 122 | // typeof null does not produce "null". The case is included here in 123 | // the remote chance that this gets fixed someday. 124 | return String(value); 125 | 126 | // If the type is "object", we might be dealing with an object or an array or 127 | // null. 128 | case "object": 129 | 130 | // Due to a specification blunder in ECMAScript, typeof null is "object", 131 | // so watch out for that case. 132 | if (!value) { 133 | return "null"; 134 | } 135 | 136 | // Check the value is not circular object. Otherwise throw TypeError. 137 | if (includes(seen, value)) { 138 | throw new TypeError("Converting circular structure to JSON"); 139 | } 140 | 141 | // Keep the value for the further check on circular references. 142 | seen.push(value); 143 | 144 | // Make an array to hold the partial results of stringifying this object value. 145 | gap += indent; 146 | partial = []; 147 | 148 | // Is the value an array? 149 | if (Object.prototype.toString.apply(value) === "[object Array]") { 150 | 151 | // The value is an array. Stringify every element. Use null as a placeholder 152 | // for non-JSON values. 153 | length = value.length; 154 | for (i = 0; i < length; i += 1) { 155 | partial[i] = str(i, value) || "null"; 156 | } 157 | 158 | // Join all of the elements together, separated with commas, and wrap them in 159 | // brackets. 160 | v = partial.length === 0 ? "[]": gap ? ("[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]") : "[" + partial.join(",") + "]"; 161 | gap = mind; 162 | return v; 163 | } 164 | 165 | // If the replacer is an array, use it to select the members to be stringified. 166 | if (rep && typeof rep === "object") { 167 | length = rep.length; 168 | for (i = 0; i < length; i += 1) { 169 | if (typeof rep[i] === "string") { 170 | k = rep[i]; 171 | v = str(k, value); 172 | if (v) { 173 | partial.push(quote(k) + ((gap) ? ": ": ":") + v); 174 | } 175 | } 176 | } 177 | } else { 178 | 179 | // Otherwise, iterate through all of the keys in the object. 180 | for (k in value) { 181 | if (Object.prototype.hasOwnProperty.call(value, k)) { 182 | v = str(k, value); 183 | if (v) { 184 | partial.push(quote(k) + ((gap) ? ": ": ":") + v); 185 | } 186 | } 187 | } 188 | } 189 | 190 | // Join all of the member texts together, separated with commas, 191 | // and wrap them in braces. 192 | v = partial.length === 0 ? "{}": gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}": "{" + partial.join(",") + "}"; 193 | gap = mind; 194 | return v; 195 | } 196 | } 197 | 198 | // If the JSON object does not yet have a stringify method, give it one. 199 | if (typeof JSON.stringify !== "function") { 200 | meta = { // table of character substitutions 201 | "\b": "\\b", 202 | "\t": "\\t", 203 | "\n": "\\n", 204 | "\f": "\\f", 205 | "\r": "\\r", 206 | "\"": "\\\"", 207 | "\\": "\\\\" 208 | }; 209 | JSON.stringify = function(value, replacer, space) { 210 | 211 | // The stringify method takes a value and an optional replacer, and an optional 212 | // space parameter, and returns a JSON text. The replacer can be a function 213 | // that can replace values, or an array of strings that will select the keys. 214 | // A default replacer method can be provided. Use of the space parameter can 215 | // produce text that is more easily readable. 216 | var i; 217 | gap = ""; 218 | indent = ""; 219 | 220 | // If the space parameter is a number, make an indent string containing that 221 | // many spaces. 222 | if (typeof space === "number") { 223 | for (i = 0; i < space; i += 1) { 224 | indent += " "; 225 | } 226 | 227 | // If the space parameter is a string, it will be used as the indent string. 228 | } else if (typeof space === "string") { 229 | indent = space; 230 | } 231 | 232 | // If there is a replacer, it must be a function or an array. 233 | // Otherwise, throw an error. 234 | rep = replacer; 235 | if (replacer && typeof replacer !== "function" && (typeof replacer !== "object" || typeof replacer.length !== "number")) { 236 | throw new Error("JSON.stringify"); 237 | } 238 | 239 | // Initialize the reference keeper. 240 | seen = []; 241 | 242 | // Make a fake root object containing our value under the key of "". 243 | // Return the result of stringifying the value. 244 | return str("", { 245 | "": value 246 | }); 247 | }; 248 | } 249 | 250 | // If the JSON object does not yet have a parse method, give it one. 251 | if (typeof JSON.parse !== "function") { 252 | JSON.parse = function(text, reviver) { 253 | 254 | // The parse method takes a text and an optional reviver function, and returns 255 | // a JavaScript value if the text is a valid JSON text. 256 | var j; 257 | 258 | function walk(holder, key) { 259 | 260 | // The walk method is used to recursively walk the resulting structure so 261 | // that modifications can be made. 262 | var k; 263 | var v; 264 | var value = holder[key]; 265 | if (value && typeof value === "object") { 266 | for (k in value) { 267 | if (Object.prototype.hasOwnProperty.call(value, k)) { 268 | v = walk(value, k); 269 | if (v !== undefined) { 270 | value[k] = v; 271 | } else { 272 | delete value[k]; 273 | } 274 | } 275 | } 276 | } 277 | return reviver.call(holder, key, value); 278 | } 279 | 280 | // Parsing happens in four stages. In the first stage, we replace certain 281 | // Unicode characters with escape sequences. JavaScript handles many characters 282 | // incorrectly, either silently deleting them, or treating them as line endings. 283 | text = String(text); 284 | rx_dangerous.lastIndex = 0; 285 | if (rx_dangerous.test(text)) { 286 | text = text.replace(rx_dangerous, 287 | function(a) { 288 | return ("\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice( - 4)); 289 | }); 290 | } 291 | 292 | // In the second stage, we run the text against regular expressions that look 293 | // for non-JSON patterns. We are especially concerned with "()" and "new" 294 | // because they can cause invocation, and "=" because it can cause mutation. 295 | // But just to be safe, we want to reject all unexpected forms. 296 | // We split the second stage into 4 regexp operations in order to work around 297 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 298 | // replace the JSON backslash pairs with "@" (a non-JSON character). Second, we 299 | // replace all simple value tokens with "]" characters. Third, we delete all 300 | // open brackets that follow a colon or comma or that begin the text. Finally, 301 | // we look to see that the remaining characters are only whitespace or "]" or 302 | // "," or ":" or "{" or "}". If that is so, then the text is safe for eval. 303 | if (rx_one.test(text.replace(rx_two, "@").replace(rx_three, "]").replace(rx_four, ""))) { 304 | 305 | // In the third stage we use the eval function to compile the text into a 306 | // JavaScript structure. The "{" operator is subject to a syntactic ambiguity 307 | // in JavaScript: it can begin a block or an object literal. We wrap the text 308 | // in parens to eliminate the ambiguity. 309 | j = eval("(" + text + ")"); 310 | 311 | // In the optional fourth stage, we recursively walk the new structure, passing 312 | // each name/value pair to a reviver function for possible transformation. 313 | return (typeof reviver === "function") ? walk({ 314 | "": j 315 | }, 316 | "") : j; 317 | } 318 | 319 | // If the text is not JSON parseable, then a SyntaxError is thrown. 320 | throw new SyntaxError("JSON.parse"); 321 | }; 322 | } 323 | } ()); 324 | 325 | // JSON 定义结束 326 | // ============ 327 | 328 | var scriptName = "Auto Color Chart"; 329 | var version = "2.23 beta"; 330 | 331 | // UI 开始 332 | // ========== 333 | var panelGlobal = this; 334 | var palette = (function() { 335 | var myPanel = (panelGlobal instanceof Panel) ? panelGlobal: new Window("palette", scriptName + " v" + version, undefined, { 336 | resizeable: true, 337 | closeButton: true 338 | }); 339 | { 340 | // EXTRACTTAB 341 | // ========== 342 | var extractTab = myPanel.add("tabbedpanel", undefined, undefined, { 343 | name: "extractTab" 344 | }); 345 | extractTab.alignChildren = "fill"; 346 | extractTab.preferredSize.width = 336.797; 347 | extractTab.margins = 0; 348 | 349 | // EXTRACTTABTEXT 350 | // ============== 351 | var extractTabText = extractTab.add("tab", undefined, undefined, { 352 | name: "extractTabText" 353 | }); 354 | extractTabText.text = "カラーグループから"; 355 | extractTabText.orientation = "column"; 356 | extractTabText.alignChildren = ["center", "top"]; 357 | extractTabText.spacing = 10; 358 | extractTabText.margins = 10; 359 | 360 | var ColorNameGroup = extractTabText.add("group", undefined, { 361 | name: "ColorNameGroup" 362 | }); 363 | ColorNameGroup.orientation = "row"; 364 | ColorNameGroup.alignChildren = ["left", "top"]; 365 | ColorNameGroup.spacing = 10; 366 | ColorNameGroup.margins = 0; 367 | ColorNameGroup.alignment = ["left", "top"]; 368 | 369 | var colorChartImageTxt = ColorNameGroup.add("statictext", undefined, undefined, { 370 | name: "colorChartImageTxt" 371 | }); 372 | colorChartImageTxt.text = "色見本名"; 373 | colorChartImageTxt.preferredSize.width = 100; 374 | colorChartImageTxt.alignment = ["left", "top"]; 375 | 376 | var Color1Disp = ColorNameGroup.add("button", undefined, undefined, { 377 | name: "Color1Disp" 378 | }); 379 | Color1Disp.size = [15, 15]; 380 | 381 | var Color2Disp = ColorNameGroup.add("button", undefined, undefined, { 382 | name: "Color2Disp" 383 | }); 384 | Color2Disp.size = [15, 15]; 385 | 386 | var Color3Disp = ColorNameGroup.add("button", undefined, undefined, { 387 | name: "Color3Disp" 388 | }); 389 | Color3Disp.size = [15, 15]; 390 | 391 | 392 | var Color4Disp = ColorNameGroup.add("button", undefined, undefined, { 393 | name: "Color4Disp" 394 | }); 395 | Color4Disp.size = [15, 15]; 396 | 397 | var Color5Disp = ColorNameGroup.add("button", undefined, undefined, { 398 | name: "Color5Disp" 399 | }); 400 | Color5Disp.size = [15, 15]; 401 | 402 | var Color6Disp = ColorNameGroup.add("button", undefined, undefined, { 403 | name: "Color6Disp" 404 | }); 405 | Color6Disp.size = [15, 15]; 406 | 407 | var Color7Disp = ColorNameGroup.add("button", undefined, undefined, { 408 | name: "Color7Disp" 409 | }); 410 | Color7Disp.size = [15, 15]; 411 | 412 | var Color8Disp = ColorNameGroup.add("button", undefined, undefined, { 413 | name: "Color8Disp" 414 | }); 415 | Color8Disp.size = [15, 15]; 416 | 417 | var Color9Disp = ColorNameGroup.add("button", undefined, undefined, { 418 | name: "Color9Disp" 419 | }); 420 | Color9Disp.size = [15, 15]; 421 | 422 | var Color10Disp = ColorNameGroup.add("button", undefined, undefined, { 423 | name: "Color10Disp" 424 | }); 425 | Color10Disp.size = [15, 15]; 426 | 427 | // EXTRACTGRUOP 428 | // ============ 429 | var extractGruop = extractTabText.add("group", undefined, { 430 | name: "extractGruop" 431 | }); 432 | extractGruop.orientation = "row"; 433 | extractGruop.alignChildren = ["left", "center"]; 434 | extractGruop.spacing = 10; 435 | extractGruop.margins = 0; 436 | 437 | var colorSlect_array = []; 438 | var colorSlect = extractGruop.add("listbox", undefined, undefined, { 439 | name: "colorSlect", 440 | items: colorSlect_array, 441 | multiselect: false, 442 | numberOfColumns: 2, 443 | columnTitles: ['番号', '色'], 444 | showHeaders: true 445 | }); 446 | colorSlect.preferredSize.width = 200; 447 | colorSlect.preferredSize.height = 200; 448 | 449 | // BTNGROUP 450 | // ======== 451 | var btnGroup = extractGruop.add("group", undefined, { 452 | name: "btnGroup" 453 | }); 454 | btnGroup.orientation = "column"; 455 | btnGroup.alignChildren = ["center", "top"]; 456 | btnGroup.spacing = 10; 457 | btnGroup.margins = 0; 458 | btnGroup.alignment = ["left", "top"]; 459 | 460 | var extractGruopAllBtn = btnGroup.add("button", undefined, undefined, { 461 | name: "extractAllBtn" 462 | }); 463 | extractGruopAllBtn.text = "グループ全部抽出"; 464 | extractGruopAllBtn.preferredSize.width = 111; 465 | extractGruopAllBtn.preferredSize.height = 40; 466 | extractGruopAllBtn.enabled = false; 467 | 468 | 469 | var colorPickerBtn = btnGroup.add("button", undefined, undefined, { 470 | name: "BtcolorPickerBtnn0" 471 | }); 472 | colorPickerBtn.text = "カラーピッカー"; 473 | colorPickerBtn.preferredSize.width = 111; 474 | colorPickerBtn.preferredSize.height = 27; 475 | colorPickerBtn.enabled = false; 476 | 477 | var pickedColorDisp = btnGroup.add("button", undefined, undefined, { 478 | name: "Btn1" 479 | }); 480 | pickedColorDisp.preferredSize.width = 111; 481 | pickedColorDisp.preferredSize.height = 5; 482 | //pickedColor.enabled = false; 483 | 484 | var Btn2 = btnGroup.add("button", undefined, undefined, { 485 | name: "Btn2" 486 | }); 487 | Btn2.preferredSize.width = 111; 488 | Btn2.preferredSize.height = 21; 489 | Btn2.enabled = false; 490 | Btn2.visible = false; 491 | 492 | 493 | var openCategroyBtn = btnGroup.add("button", undefined, undefined, { 494 | name: "openCategroyBtn" 495 | }); 496 | openCategroyBtn.text = "タイプパネル開く"; 497 | openCategroyBtn.preferredSize.width = 111; 498 | openCategroyBtn.enabled = false; 499 | 500 | 501 | var openFileBtn = btnGroup.add("button", undefined, undefined, { 502 | name: "openFileBtn" 503 | }); 504 | openFileBtn.text = "色見本データ開く"; 505 | openFileBtn.preferredSize.width = 111; 506 | 507 | /* 508 | // COLORPICKERTAB 509 | // =========== 510 | var colorPickerTab = extractTab.add("tab", undefined, undefined, { 511 | name: "colorPickerTab" 512 | }); 513 | colorPickerTab.text = "吸色"; 514 | colorPickerTab.orientation = "column"; 515 | colorPickerTab.alignChildren = ["center", "center"]; 516 | colorPickerTab.spacing = 10; 517 | colorPickerTab.margins = 10; 518 | var colorPickerBtn = colorPickerTab.add("button", undefined, undefined, { 519 | name: "colorPickerBtn" 520 | }); 521 | colorPickerBtn.text = "颜色拾取器"; 522 | colorPickerBtn.preferredSize.width = 105; 523 | colorPickerBtn.preferredSize.height = 40; 524 | var msgText = colorPickerTab.add("edittext", undefined, undefined, { 525 | name: "msgText" 526 | }); 527 | msgText.preferredSize.width = 110; 528 | msgText.text = "R: G: B: "; 529 | 530 | 531 | // CONFIGTAB 532 | // ========= 533 | var configTab = extractTab.add("tab", undefined, undefined, { 534 | name: "configTab" 535 | }); 536 | configTab.text = "配置"; 537 | configTab.orientation = "row"; 538 | configTab.alignChildren = ["left", "top"]; 539 | configTab.spacing = 10; 540 | configTab.margins = 10; 541 | */ 542 | } 543 | //全局变量 544 | //=========== 545 | var colorChartJSON = {}; 546 | var myWindow; 547 | const myBlack = [0, 0, 0]; 548 | 549 | // 旧版JSON映射表 550 | const keyMappings = { 551 | "hi": "Hi", 552 | "normal": "ノーマル", 553 | "shadow": "1号影", 554 | "2nd_shadow": "2号影", 555 | "hi_in_shadow": "影中Hi", 556 | "hi_in_2nd_shadow": "2号影中Hi", 557 | "shadow_s_hi": "影のHi", 558 | "2nd_shadow_s_hi": "2号影のHi", 559 | "2nd_shadow_hi": "2号影のHi", 560 | "normal_tp": "TP" 561 | }; 562 | 563 | var userSelection = { 564 | extractColorMode: "選択中クループのみ" 565 | }; 566 | 567 | const colorDisplays = [Color1Disp, Color2Disp, Color3Disp, 568 | Color4Disp, Color5Disp, Color6Disp, 569 | Color7Disp, Color8Disp, Color9Disp, 570 | Color10Disp]; 571 | 572 | // 获取配置文件夹(暂未使用) 573 | // ======================= 574 | function getConfigFolder() { 575 | var userDataFolder = Folder.userData; 576 | var aescriptsFolder = Folder(userDataFolder.toString() + "/AutoColorChart/Config"); 577 | if (!aescriptsFolder.exists) { 578 | var checkFolder = aescriptsFolder.create(); 579 | if (!checkFolder) { 580 | alert("未能创建配置文件!",scriptName); 581 | aescriptsFolder = Folder.temp; 582 | } 583 | } 584 | return aescriptsFolder.toString(); 585 | } 586 | 587 | var configFolder = getConfigFolder() 588 | var config = {}; 589 | 590 | // 更新配置(暂未使用) 591 | // ======================= 592 | function updateConfig() { 593 | var configFolder = getConfigFolder() 594 | var file = File(configFolder + "/config.json"); 595 | // 检查文件是否存在且不为空 596 | if (file.exists && file.length > 0) { 597 | // 打开文件以读取模式 598 | if (file.open("r")) { 599 | // 读取文件内容 600 | var content = file.read(); 601 | // 将文件内容解析为一个对象 602 | var parsedConfig = JSON.parse(content); 603 | 604 | // 关闭文件 605 | file.close(); 606 | 607 | // 更新config对象的值 608 | config["colors"] = parsedConfig["colors"]; 609 | config["override_colors"] = parsedConfig["override_colors"]; 610 | config["distance"] = parsedConfig["distance"]; 611 | config["image_path"] = parsedConfig["image_path"]; 612 | config["save_path"] = parsedConfig["save_path"]; 613 | 614 | } else { 615 | alert("无法打开文件:" + file.fsName, scriptName); 616 | } 617 | } 618 | 619 | } 620 | 621 | 622 | // 颜色显示器按钮 623 | // ============ 624 | function setColorDisp(disp, color, visible) { 625 | function customDraw() { 626 | with(this) { 627 | graphics.drawOSControl(); 628 | graphics.rectPath(0, 0, size[0], size[1]); 629 | graphics.fillPath(fillBrush); 630 | } 631 | } 632 | disp.fillBrush = disp.graphics.newBrush(disp.graphics.BrushType.SOLID_COLOR, color); 633 | disp.onDraw = customDraw; 634 | disp.enabled = false; 635 | disp.enabled = true; 636 | disp.enabled = visible; 637 | disp.visible = visible; 638 | } 639 | 640 | // 初始化颜色显示器 641 | // =============== 642 | function initColorDisp(allDisp){ 643 | for (var i = 0; i < colorDisplays.length; i++) { 644 | setColorDisp(colorDisplays[i], myBlack, false); 645 | } 646 | if (allDisp) setColorDisp(pickedColorDisp, myBlack, false); 647 | } 648 | 649 | initColorDisp(1); 650 | 651 | // 函数 652 | // ========= 653 | // 调用AE颜色拾取器 654 | // =============== 655 | function colorPicker(startValue) { 656 | // 活动合成 657 | if (!app.project.activeItem || !(app.project.activeItem instanceof CompItem)) { 658 | alert("コンポを開いてください!", scriptName); 659 | return []; 660 | } 661 | 662 | // 临时图层 663 | var tempLayer = app.project.activeItem.layers.addShape(); 664 | var newColorControl = tempLayer("ADBE Effect Parade").addProperty("ADBE Color Control"); 665 | var theColorProp = newColorControl("ADBE Color Control-0001"); 666 | 667 | // s初始值 668 | if (startValue && startValue.length == 3) { 669 | theColorProp.setValue(startValue); 670 | } 671 | 672 | // 执行 673 | var editValueID = 2240 // or app.findMenuCommandId("Edit Value..."); 674 | theColorProp.selected = true; 675 | app.executeCommand(editValueID); 676 | 677 | // 获取结果 678 | var result = theColorProp.value; 679 | 680 | // 删除临时层 681 | if (tempLayer) { 682 | tempLayer.remove(); 683 | } 684 | return result; 685 | } 686 | 687 | // RGB转HEX 688 | // ================= 689 | function rgbToHex(rgb) { 690 | var hex = []; 691 | for (var i = 0; i < 3; i++) { 692 | var val = Math.round(rgb[i] * 255); 693 | var hexVal = val.toString(16); 694 | if (hexVal.length < 2) { 695 | hexVal = "0" + hexVal; 696 | } 697 | hex.push(hexVal.toUpperCase()); 698 | } 699 | return hex.join(""); 700 | } 701 | 702 | // 查找JSON中指定颜色 703 | // ================= 704 | function findColorInJson(parsedJson, color) { 705 | var myColorStr = JSON.stringify(color); 706 | 707 | for (var imageKey in parsedJson) { 708 | var colorGroups = parsedJson[imageKey]; 709 | 710 | for (var groupKey in colorGroups) { 711 | var colors = colorGroups[groupKey]; 712 | 713 | for (var colorKey in colors) { 714 | var colorArray = colors[colorKey]; 715 | 716 | if (JSON.stringify(colorArray) === myColorStr) { 717 | return groupKey; 718 | } 719 | } 720 | } 721 | } 722 | 723 | return null; 724 | } 725 | 726 | // 字符串颜色数组 727 | // ============= 728 | function parseColorStr(colorStr) { 729 | var colorArray = colorStr.slice(1, -1).split(','); 730 | for (var i = 0; i < colorArray.length; i++) { 731 | colorArray[i] = +colorArray[i] / 255; 732 | } 733 | return colorArray; 734 | } 735 | 736 | // 创建按颜色类型去色窗口 737 | // ==================== 738 | function createButtonsFromJson(jsonData) { 739 | var myWindow = new Window("palette", "色タイプから", undefined); 740 | 741 | for (var imageName in jsonData) { 742 | var imageGroup = myWindow.add("group", undefined, imageName); 743 | imageGroup.orientation = "column"; 744 | imageGroup.alignChildren = ["left", "center"] 745 | imageGroup.add("statictext", undefined, "色見本 " + imageName + " 中の色"); 746 | // 添加单选按钮组 747 | var radioGroup = imageGroup.add("group", undefined); 748 | radioGroup.orientation = "row"; 749 | radioGroup.add("statictext", undefined, "抽出参照モード:"); 750 | var allColorsRadio = radioGroup.add("radiobutton", undefined, "色見本全部"); 751 | var singleColorRadio = radioGroup.add("radiobutton", undefined, "選択中クループのみ"); 752 | singleColorRadio.value = true; // 默认选择"仅从选中的组" 753 | 754 | allColorsRadio.onClick = function() { userSelection.extractColorMode = "色見本全部"; } 755 | singleColorRadio.onClick = function() { userSelection.extractColorMode = "選択中クループのみ"; } 756 | 757 | 758 | var addedKeys = {}; // 用于跟踪已添加的键 759 | var buttonRow; // 当前行 760 | var buttonCount = 0; // 当前行的按钮计数 761 | 762 | for (var numberKey in jsonData[imageName]) { 763 | for (var itemKey in jsonData[imageName][numberKey]) { 764 | if (!addedKeys[itemKey]) { 765 | // 每3个按钮开始一个新的行 766 | if (buttonCount % 4 === 0) { 767 | buttonRow = imageGroup.add("group", undefined, {orientation: "row"}); 768 | 769 | } 770 | var buttonText = keyMappings[itemKey] || itemKey; // 如果没有找到映射,则使用原始键值 771 | var btn = buttonRow.add("button", undefined, buttonText); 772 | btn.preferredSize.width = 80; 773 | btn.preferredSize.height = 25; 774 | btn.onClick = function(key) { 775 | return function() { 776 | if(userSelection.extractColorMode === "選択中クループのみ") extractOneColor(key); 777 | else if(userSelection.extractColorMode === "色見本全部") extractAllColor(key); 778 | 779 | }; 780 | }(itemKey); 781 | addedKeys[itemKey] = true; 782 | buttonCount++; 783 | } 784 | } 785 | } 786 | } 787 | 788 | myWindow.center(); 789 | myWindow.show(); 790 | return myWindow; 791 | 792 | } 793 | 794 | // 提取选择组中单一指定颜色 795 | // =============== 796 | function extractOneColor(colorType) { 797 | if(!app.project.activeItem) {alert("コンポを開いてください", scriptName); return;} 798 | var selectedLayers = app.project.activeItem.selectedLayers; 799 | var newLayer; 800 | if (selectedLayers.length > 0) { 801 | var selectedItems = colorSlect.selection; 802 | if (!selectedItems) { 803 | alert("リストから一つ選んでください.", scriptName); 804 | return; 805 | } 806 | 807 | if (! (selectedItems instanceof Array)) { 808 | selectedItems = [selectedItems]; 809 | } 810 | 811 | for (var i = 0; i < selectedLayers.length; i++) { 812 | var originalLayer = selectedLayers[i]; 813 | newLayer = originalLayer.duplicate(); 814 | var colorEffect = newLayer.property("Effects").addProperty("Color Keep"); 815 | 816 | for (var j = 0; j < selectedItems.length; j++) { 817 | var selectedColorText = selectedItems[j].subItems[0].text; 818 | var colorParts = selectedColorText.split(";"); 819 | 820 | var color; 821 | 822 | for (var k = 0; k < colorParts.length; k++) { 823 | var parts = colorParts[k].split(":"); 824 | if (parts[0] === colorType) { 825 | color = parts[1]; 826 | try { 827 | colorEffect.property(1).setValue(1); 828 | colorEffect.property(2).setValue(parseColorStr(color)); 829 | } catch(e) { 830 | alert("抽出時にエラー: " + e, scriptName); 831 | } 832 | } 833 | } 834 | 835 | } 836 | } 837 | } else { 838 | alert("レイヤー一つ選んでください", scriptName); 839 | } 840 | return newLayer 841 | } 842 | 843 | // 提取色见全部指定颜色 844 | // =============== 845 | function extractAllColor(colorType) { 846 | var selectedLayers = app.project.activeItem.selectedLayers; 847 | var newLayer; 848 | if (selectedLayers.length > 0) { 849 | try { 850 | var resultArray = []; 851 | for (var key in colorChartJSON) { 852 | var innerObj = colorChartJSON[key]; 853 | for (var innerKey in innerObj) { 854 | if (innerObj[innerKey].hasOwnProperty(colorType)) { 855 | var colorArray = innerObj[innerKey][colorType]; 856 | for (var i = 0; i < colorArray.length; i++) { 857 | colorArray[i] = colorArray[i] / 255; 858 | } 859 | resultArray.push(colorArray); 860 | } 861 | } 862 | } 863 | 864 | 865 | for (var j = 0; j < selectedLayers.length; j++) { 866 | var originalLayer = selectedLayers[j]; 867 | newLayer = originalLayer.duplicate(); 868 | var colorEffect = newLayer.property("Effects").addProperty("Color Keep"); 869 | for (var i = 0; i < resultArray.length; i++) { 870 | colorEffect.property(1).setValue(resultArray.length); 871 | colorEffect.property(i + 2).setValue(resultArray[i]); 872 | } 873 | } 874 | 875 | } catch(e) { 876 | alert(e); 877 | } 878 | } else alert("レイヤー一つ選んでください", scriptName); 879 | return newLayer; 880 | } 881 | 882 | // 解析 XML 返回 JSON 883 | // ================= 884 | function parseXMLtoJSON(xmlData) { 885 | var jsonData = {}; 886 | var imageList = xmlData.image; 887 | for (var i = 0; i < imageList.length(); i++) { 888 | var image = imageList[i]; 889 | var imageName = image.@name.toString(); 890 | jsonData[imageName] = {}; 891 | 892 | var groupList = image.group; 893 | for (var j = 0; j < groupList.length(); j++) { 894 | var group = groupList[j]; 895 | var groupId = group.@id.toString(); 896 | jsonData[imageName][groupId] = {}; 897 | 898 | var colorList = group.color; 899 | for (var k = 0; k < colorList.length(); k++) { 900 | var color = colorList[k]; 901 | var colorType = color.@colorType.toString(); 902 | jsonData[imageName][groupId][colorType] = [ 903 | parseInt(color.@r.toString()), 904 | parseInt(color.@g.toString()), 905 | parseInt(color.@b.toString()) 906 | ]; 907 | } 908 | } 909 | } 910 | return jsonData; 911 | } 912 | 913 | // 转换新JSON 914 | // =========== 915 | function convertNewJSON(newJson) { 916 | // 新的JSON对象 917 | var oldJSON = {}; 918 | 919 | // 从原始JSON获取图像名称,并转换为.tga格式 920 | var imageName = newJson.colorChartData.image.name; 921 | var fileName = imageName; 922 | 923 | // 初始化文件名键值 924 | oldJSON[fileName] = {}; 925 | 926 | // 获取所有组 927 | var groups = newJson.colorChartData.image.group; 928 | var groupCount = groups.length; 929 | 930 | // 遍历所有组 931 | for (var i = 0; i < groupCount; i++) { 932 | var group = groups[i]; 933 | var groupId = group.id.toString(); 934 | 935 | oldJSON[fileName][groupId] = {}; 936 | 937 | // 检查color数据是单个对象还是数组 938 | var colorData = group.color; 939 | var isColorArray = Object.prototype.toString.call(colorData) === '[object Array]'; 940 | 941 | if (group.validBoxNum === 1 && !isColorArray) { 942 | // 如果是单个颜色对象 943 | var color = colorData; 944 | oldJSON[fileName][groupId][color.colorType] = color.RGB; 945 | } else if (group.validBoxNum && isColorArray && colorData.length === group.validBoxNum) { 946 | // 如果是颜色数组 947 | for (var j = 0; j < colorData.length; j++) { 948 | var color = colorData[j]; 949 | oldJSON[fileName][groupId][color.colorType] = color.RGB; 950 | } 951 | } 952 | } 953 | 954 | return oldJSON; 955 | } 956 | // 颜色拾取器单击事件 957 | // ================= 958 | colorPickerBtn.onClick = function() { 959 | 960 | var myColor = colorPicker([1, 0, 0]); 961 | // var color1 = "R: " + Math.round(myColor[0] * 255).toString() + " G: " + Math.round(myColor[1] * 255).toString() + " B: " + Math.round(myColor[2] * 255).toString() + "\n"; 962 | // msgText.text = String(color1); 963 | var myColorN = [Math.round(myColor[0] * 255), Math.round(myColor[1] * 255), Math.round(myColor[2] * 255)]; 964 | setColorDisp(pickedColorDisp, myColor, true); 965 | 966 | try { 967 | var groupNumber = findColorInJson(colorChartJSON, myColorN); 968 | if (groupNumber != null) { 969 | colorSlect.selection = null; 970 | colorSlect.selection = colorSlect.find(groupNumber.toString()); 971 | 972 | } else { 973 | alert("指定の色が見つかりませんでした,色見本データを確認してください。", scriptName); 974 | } 975 | 976 | } catch(e) { 977 | alert(e, scriptName); 978 | } 979 | } 980 | 981 | // 列表选择事件 982 | // =========== 983 | colorSlect.onChange = function() { 984 | function parseColor(color) { 985 | var colorArray = color.slice(1, -1).split(','); 986 | for (var i = 0; i < colorArray.length; i++) { 987 | colorArray[i] = +colorArray[i] / 255; 988 | } 989 | return colorArray; 990 | } 991 | 992 | var selectedItems = colorSlect.selection; 993 | if (!selectedItems) return; 994 | if (!(selectedItems instanceof Array)) { 995 | selectedItems = [selectedItems]; 996 | } 997 | 998 | var selectedColorText = selectedItems[0].subItems[0].text; 999 | var colorParts = selectedColorText.split(";"); 1000 | 1001 | // 重置所有颜色显示器 1002 | initColorDisp(0); 1003 | 1004 | // 设置新的颜色 1005 | for (var i = 0; i < colorParts.length && i < colorDisplays.length; i++) { 1006 | var parts = colorParts[i].split(":"); 1007 | var color = parseColor(parts[1]); 1008 | setColorDisp(colorDisplays[i], color, true); 1009 | } 1010 | } 1011 | 1012 | // 打开按钮单击事件 1013 | // =============== 1014 | openFileBtn.onClick = function() { 1015 | if(typeof myWindow == 'object') myWindow.close() 1016 | var file = File.openDialog("请打开色见数据文件", "JSON Files:*.json;XML Files:*.xml"); 1017 | if (file !== null) { 1018 | var fileNameArray = file.name.toLowerCase().split('.'); 1019 | var fileExtension = fileNameArray[fileNameArray.length - 1]; 1020 | 1021 | file.open('r'); 1022 | var content = file.read(); 1023 | file.close(); 1024 | 1025 | if (fileExtension === 'json') { 1026 | // 解析 JSON 文件 1027 | colorChartJSON = JSON.parse(content); 1028 | // 检查是否为新版JSON 1029 | if(colorChartJSON.colorChartData !== undefined) 1030 | colorChartJSON = convertNewJSON(colorChartJSON); 1031 | // 启用相应的按钮 1032 | openCategroyBtn.enabled = true; 1033 | colorPickerBtn.enabled = true; 1034 | extractGruopAllBtn.enabled = true; 1035 | } else if (fileExtension === 'xml') { 1036 | // 解析 XML 文件 1037 | var xmlData = new XML(content); 1038 | colorChartJSON = parseXMLtoJSON(xmlData); 1039 | // 启用相应的按钮 1040 | openCategroyBtn.enabled = true; 1041 | colorPickerBtn.enabled = true; 1042 | extractGruopAllBtn.enabled = true; 1043 | } else { 1044 | alert('无效的文件类型。请选择 JSON 或 XML 文件。', scriptName); 1045 | } 1046 | } 1047 | myWindow = createButtonsFromJson(colorChartJSON); 1048 | // 更新 colorChartImageTxt.text 1049 | for (var key in colorChartJSON) { 1050 | colorChartImageTxt.text = key; 1051 | imputImage = key; 1052 | break; // 只取第一个 key 图片名称 1053 | } 1054 | 1055 | // 更新 listbox 1056 | colorSlect.removeAll(); // 清除原有列表 1057 | for (var key in colorChartJSON) { 1058 | var colorInfo = colorChartJSON[key]; 1059 | for (var group in colorInfo) { 1060 | var colors = colorInfo[group]; 1061 | var colorStr = ""; 1062 | for (var colorName in colors) { 1063 | var colorItem = colors[colorName]; 1064 | //colorName = colorName.charAt(0).toUpperCase() + colorName.slice(1); // 将首字母大写 1065 | colorStr += colorName + ':[' + colorItem.toString() + '];'; 1066 | } 1067 | var item = colorSlect.add('item', group); 1068 | item.subItems[0].text = colorStr; // 'Color' 列 1069 | } 1070 | } 1071 | }; 1072 | 1073 | openCategroyBtn.onClick = function(){ 1074 | createButtonsFromJson(colorChartJSON); 1075 | } 1076 | 1077 | // 提取全部组按钮单击事件 1078 | // ===================== 1079 | extractGruopAllBtn.onClick = function() { 1080 | var selectedLayers = app.project.activeItem.selectedLayers; 1081 | if (selectedLayers.length > 0) { 1082 | for (var i = 0; i < selectedLayers.length; i++) { 1083 | var originalLayer = selectedLayers[i]; 1084 | var selectedItems = colorSlect.selection; 1085 | 1086 | if (!selectedItems) { 1087 | alert("リストから一つ選んでください", scriptName); 1088 | return; 1089 | } 1090 | 1091 | if (!(selectedItems instanceof Array)) { 1092 | selectedItems = [selectedItems]; 1093 | } 1094 | 1095 | var newLayer = originalLayer.duplicate(); 1096 | var colorEffect = newLayer.property("Effects").addProperty("Color Keep"); 1097 | 1098 | for (var j = 0; j < selectedItems.length; j++) { 1099 | var selectedItem = selectedItems[j]; 1100 | if (!selectedItem || !selectedItem.subItems || selectedItem.subItems.length === 0) { 1101 | continue; // 跳过无效的选项 1102 | } 1103 | 1104 | var selectedColorText = selectedItem.subItems[0].text; 1105 | var colorParts = selectedColorText.split(";"); 1106 | 1107 | try { 1108 | colorEffect.property(1).setValue(colorParts.length - 1); 1109 | 1110 | for (var k = 0; k < colorParts.length; k++) { 1111 | var parts = colorParts[k].split(":"); 1112 | if (parts.length < 2) { 1113 | continue; // 跳过无效的颜色部分 1114 | } 1115 | var colorArray = parts[1].slice(1, -1).split(','); 1116 | 1117 | for (var l = 0; l < colorArray.length; l++) { 1118 | colorArray[l] = +colorArray[l] / 255; 1119 | } 1120 | colorEffect.property(k + 2).setValue(colorArray); 1121 | } 1122 | 1123 | } catch(e) { 1124 | alert(e); 1125 | } 1126 | } 1127 | } 1128 | } else { 1129 | alert("レイヤー一つ選んでください!", scriptName); 1130 | } 1131 | } 1132 | 1133 | // UI 结束 1134 | // ========== 1135 | myPanel.layout.layout(true); 1136 | myPanel.layout.resize(); 1137 | myPanel.onResizing = myPanel.onResize = function() { 1138 | this.layout.resize() 1139 | } 1140 | if (myPanel instanceof Window) myPanel.show(); 1141 | return myPanel; 1142 | } ()) 1143 | -------------------------------------------------------------------------------- /AEScripts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SengokuMayoi (Ma Chenxing) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | WARNING 4 | THIS MIT LICENSE IS ONLY FOR AFTER EFFECTS SCRIPTS 5 | 6 | Copyright (c) 2024 SengokuMayoi (Ma Chenxing) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | [日本語](README.md) | [简体中文](README.zh.md) | [English](README.en.md) 2 | 3 | # Auto Color Chart 2 4 | 5 | ## Overview 6 | - This project is a desktop application and an accompanying AE script designed to convert color charts in 2D animations into **color data categorized by parts (hair, skin, etc.) and color types (highlight, normal, etc.)**, facilitating color extraction by parts during the shooting process. 7 | - It can **automatically recognize** color boxes in the color charts and determine color types (highlight, standard, shadow, secondary shadow, etc.), greatly improving the efficiency of color extraction in the post-production stage. 8 | - Supports single or batch processing mode, allowing for quick handling of a large number of color charts. It can detect duplicate colors within the color chart and mark them on the image. 9 | - Through the **Adobe After Effects** script, you can extract colors from color charts by parts (such as skin, hair, clothes, etc.) or by types (highlight, standard, etc.) within AE. 10 | - Cygames, Inc. has developed a color chart recognition tool, but it can only recognize color charts within the Princess Connect! game animations and requires a color chart template. This software, with its unique algorithm, can recognize a wider range of color chart color boxes and, through the introduction of a preset (user-defined box) system, can recognize almost all color charts. It can be applied to various workflows. 11 | 12 | **Can be used immediately without changing the existing workflow.** 13 | 14 | ![Auto Color Chart 2 Main Window](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/mian_cn.png "Auto Color Chart 2 Main Window") 15 | *Auto Color Chart 2 Main Window* 16 | *The color chart used in the demo has been publicly licensed* 17 | 18 | ## Features 19 | - **Automatic recognition of color boxes**: Uses image processing technology to automatically recognize the colors and their types within the color chart and group them. 20 | - **Threshold recognition to separate color boxes**: Not only ordinary color boxes, but also correctly recognizes separated highlight, shadow, and standard color boxes. 21 | - **Color chart preview**: Allows you to zoom in, zoom out, and drag to inspect the color chart with the mouse. 22 | - **Ignore color function**: The color box should be colorless, but it may actually be a fake color of the background. Input the fake color into the ignore color, and it will be automatically excluded during recognition. 23 | - **Custom color box management**: Supports special color boxes for user customization and management, supporting almost all color charts. 24 | - **Settings file editing and debug information window**: Provides configuration file editing and debug information window for developers to troubleshoot and optimize. 25 | - **Supports single or batch processing**: The software supports single image or batch mode processing, improving work efficiency. 26 | - **Duplicate color detection**: Can detect the same color in the color chart and mark it for inspection. 27 | - **Provides AE script**: Provides Adobe After Effects script to extract specified colors in AE according to the colors in the color chart (all part colors or single color). 28 | - The AE script is open-source and follows the MIT open-source license, and can be used with ffx, etc., for automatic shooting. 29 | - **Convenience of color chart data**: The color chart data is saved as JSON or XML files, allowing users to easily access and edit. 30 | - The XML file contains all the information read from the color chart, making the color chart data more versatile. 31 | 32 | *Old JSON Example* 33 | ```JSON 34 | { 35 | "001_01_huai_normal.png": { 36 | "1": { 37 | "hi": [ 38 | 255, 39 | 165, 40 | 0 41 | ], 42 | "normal": [ 43 | 0, 44 | 255, 45 | 165 46 | ], 47 | "shadow": [ 48 | 255, 49 | 165, 50 | 0 51 | ], 52 | "2nd_shadow": [ 53 | 255, 54 | 165, 55 | 0 56 | ] 57 | }, 58 | "2": { 59 | "hi": [ 60 | 0, 61 | 255, 62 | 165 63 | ], 64 | "normal": [ 65 | 0, 66 | 255, 67 | 165 68 | ], 69 | "shadow": [ 70 | 0, 71 | 255, 72 | 0 73 | ], 74 | "2nd_shadow": [ 75 | 255, 76 | 165, 77 | 0 78 | ] 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | *Old XML Example* 85 | ```XML 86 | 87 | 88 | 89 | 90 | 91 | 644 92 | 93 | 94 | 95 | 2196 96 | 97 | 98 | 99 | 2196 100 | 101 | 102 | 103 | 2196 104 | 105 | 106 | 107 | 108 | 109 | 644 110 | 111 | 112 | 113 | 2196 114 | 115 | 116 | 117 | 2196 118 | 119 | 120 | 121 | 2196 122 | 123 | 124 | 125 | 126 | 127 | ``` 128 | 129 | ### In future updates, a new JSON and XML data format will be adopted. The new JSON contains more information, facilitating future development and expansion. 130 | ### The AE script has been updated to support both the old and new JSON and XML formats. 131 | *Note: Depending on the situation, JSON data may be abandoned in favor of XML in the future.* 132 | 133 | *New JSON Example* 134 | ```JSON 135 | { 136 | "colorChartData": { 137 | "programVersion": 2.23, 138 | "dataVersion": 1.1, 139 | "markedImgPath": "C:\\this\\is\\a\\sample\\path\\image_marked_colors.png", 140 | "image": { 141 | "name": "image.png", 142 | "path": "C:\\this\\is\\a\\sample\\path\\image.png", 143 | "width": 4000, 144 | "height": 2000, 145 | "gruopNum": 20, 146 | "group": [ 147 | { 148 | "id": 1, 149 | "boxNum": 5, 150 | "validBoxNum": 5, 151 | "orientation": "vertical", 152 | "tag": "", 153 | "groupBoxInfo": { 154 | "boxCordLU": [1000, 50], 155 | "boxCordRD": [1100, 200], 156 | "centerCord": [1050.0, 125.0] 157 | }, 158 | "color": [ 159 | { 160 | "boxID": 1, 161 | "colorType": "hi", 162 | "area": 1000, 163 | "position": [1020.5, 60.0], 164 | "RGB": [255, 0, 0] 165 | }, 166 | { 167 | "boxID": 2, 168 | "colorType": "normal", 169 | "area": 2000, 170 | "position": [1010.5, 100.5], 171 | "RGB": [0, 255, 0] 172 | }, 173 | { 174 | "boxID": 3, 175 | "colorType": "shadow_s_hi", 176 | "area": 300, 177 | "position": [1040.5, 120.0], 178 | "RGB": [0, 0, 255] 179 | }, 180 | { 181 | "boxID": 4, 182 | "colorType": "shadow", 183 | "area": 2000, 184 | "position": [1010.5, 140.5], 185 | "RGB": [255, 255, 0] 186 | }, 187 | { 188 | "boxID": 5, 189 | "colorType": "2nd_shadow", 190 | "area": 2000, 191 | "position": [1010.5, 180.5], 192 | "RGB": [0, 255, 255] 193 | } 194 | ] 195 | } 196 | ] 197 | 198 | 199 | } 200 | } 201 | } 202 | ``` 203 | 204 | *New XML Example* 205 | ```XML 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | ``` 219 | 220 | ## Tech Stacks 221 | ### This project is mainly developed using Python and includes the following libraries and technologies: 222 | - **Image Processing**: Pillow 223 | - **Image Analysis**: scikit-image 224 | - **UI Design**: Tkinter, ttkthemes 225 | - **Data Processing and Settings**: JSON, XML (may abandon JSON data in favor of XML in the future) 226 | ### The AE script is written in JavaScript. 227 | - Supports JSON and XML color chart data 228 | - Uses AE's built-in color selection tool (eye-dropper toll) 229 | - Displays corresponding colors when selecting color groups and sampling colors for easy viewing 230 | - Color picking plugin: OLM Color Keep 231 | - **Note: The AE script follows the MIT license.** 232 | 233 | ![AE Script Main Window](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/aescriptmain.png "AE Script Main Window") 234 | 235 | ![AE Script Window 2](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/aescripttypepanel.png "AE Script Window 2") 236 | 237 | ## Use Cases 238 | This software and script are suitable for workflows that commonly use color charts in animation production. 239 | 240 | ## Features Screenshots 241 | 242 | ![Color Box Recognition](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/usrboxreg.png "Color Box Recognition") 243 | 244 | ![Color Box Management](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/usrboxmgn.png "Color Box Management") 245 | 246 | ![Color Box Diversity](https://github.com/ChenxingM/AutoColorChart/blob/main/supportedBoxes/03_00.PNG "Color Box Diversity") 247 | 248 | ![Color Picker](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/colorpicker.gif "Color Picker") 249 | 250 | ## Releases 251 | Please download from the Release section. 252 | - AutoColorChart_2.22.exe (Simplified Chinese) 253 | - AutoColorChart_2.22_JP.exe (Japanese) 254 | - AutoColorChartBeta_CN.jsx (Simplified Chinese AE Script) 255 | - AutoColorChartBeta_JP.jsx (Japanese AE Script) 256 | - AutoColorChar2_UserGuide.pdf (In Production) 257 | 258 | ## Verified Software Environment 259 | - **Software Body**: Windows 10 260 | - **AE Script**: Adobe After Effect 2023 using JavaScript debugger 261 | - **Other AE and Windows versions should work as long as they are not too old.** 262 | - **Mac version has been tested to some extent, but most photography plugins are not supported, so it is not recommended or released. If needed, an Apple Silicon version may be released.** 263 | - **AE script also runs on Mac.** 264 | 265 | ## Contributions 266 | 267 | Feel free to commit issuses and pull requests in order to make this project better. 268 | 269 | ## Other 270 | - The source code for Auto Color Chart 1.0 has been released! 271 | - Cygames Technical Conference article link: [【Cygames Tech Conference フォローアップ】『プリンセスコネクト!Re:Dive』アニメ撮影におけるテクニカルアーティストの役割~最高のアニメRPGを作るための自動化制作事例~](https://tech.cygames.co.jp/archives/3516/) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [日本語](README.md) | [简体中文](README.zh.md) | [English](README.en.md) 2 | # 色見本認識 および 撮影処理における色抽出ツール 3 | # Auto Color Chart 2 4 | 5 | # Auto Color Chart 2.4.0 は年内にリリース予定です。 6 | - ダークのUIと完全に新しい色見本データフォーマット。色見本データ閲覧・編集ソフトに加え、AEの方も一括色見本データを読み込み可能に。 7 | - アップデートチェック機能とアクティベート機能が実装されています。お楽しみに。 8 | ![2.4主窗口](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/2.4main.PNG "Auto Color Chart 2.4.0 メインウィンド") 9 | 10 | ## *ユーザーガイドブック作成中、今しばらくお待ちください。* 11 | 12 | ## 概要 13 | - 本プロジェクトは、作画アニメーションから色見本画像を**色見本で指定したパーツ(肌、髪など)ごとをクループに、色の種類(Hi、ノーマル)も含めた色見本データに変換**し、撮影時に色を容易に抽出できるようにする**デスクトップアプリケーション**と併せて使用する**AEスクリプト**です。 14 | - 色見本内のカラーボックスを**自動的に識別**し、色タイプ(ハイライト、ノーマル、影、2号影など)も判定できるよう設計されており、ポスプロ段階での色抽出の効率を大幅に向上させます。 15 | - 単一または一括処理モードをサポートしており、大量の色見本も素早くデータ化できます。色見本内の重複色が検出でき、色見本画像にマークすることも可能です。 16 | - **Adobe After Effects**用のスクリプトを通じてAE内で色見本の色をパーツごとに(肌、髪、服など)、もしくはタイプごとに(Hi、ノーマルなど)の抽出を実現しています。 17 | - Cygamesさんは、色見本の識別ツールを既に開発してはいますが、公開されている情報では、限られたボックスしか認識できないそうです。公式サイトでは、「調整する際はカラーボックスに合わせたテンプレートを用意する必要がある」と書いてあります。本ソフトは、独自のアルゴリズムで、幅広いボックスを認識でき、更にプリセットシステムの導入により、ほぼ全ての色見本を認識できます。様々な工程で使用できるでしょう。 18 | 19 | **現在の制作工程を全く改変せず、即使用できるツールとなります。** 20 | 21 | ![主窗口](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/mian.png "Auto Color Chart 2 メインウィンド") 22 | *Auto Color Chart 2 メインウィンド* 23 | *デモンストレーションで使用された色見本ファイルの公開許可を得ています* 24 | ## 特徴 25 | - **カラーボックスの自動識別**: 画像処理技術を利用して色見本内の色とそのタイプを自動的に識別し、グループ分けします。 26 | - **閾値による分離ボックスの認識**: 通常ボックスはもちろん、ハイライトやハイライトと影中ハイライトがノーマルなどと離れていても、正しく認識できます。 27 | - **色見本プレビュー**: マウスで拡大、縮小、ドラッグし、色見本を確認できます。 28 | - **無視色機能**: カラーボックスに無色のはずですが、実際に背景の仮色である場合、仮色を無視色に入力すれば、識別時に自動的カットします。 29 | - **カスタムカラーボックス管理**: 作品によっては、特殊のカラーボックスのユーザーによるカスタマイズと管理をサポートし、ほぼ全ての色見本に対応できます。 30 | - **設定ファイルの編集およびデバッグ情報ウィンドウ**: デベロッパー向けに設定ファイルの編集も可能、トラブルシューティングと最適化のためのデバッグ情報ウィンドウを提供します。 31 | - **単一または一括処理のサポート**: ソフトウェアは、個々の画像または一括モードでの処理をサポートし、作業効率を向上させます。 32 | - **重複色の検出**: 色見本にある同じ色の箇所を検知し、マークします。色見本の誤りや色の把握に最適です。 33 | - **AEスクリプトの提供**: AE内で色見本の色を基に、セルの指定色(パーツ色全部、もしくは単一色)を抽出するためのAdobe After Effectsスクリプトを提供します。 34 | - AEスクリプトはオープンソースしており、ffxなどと連携し、自動化撮影(キャラ自動処理など)が実現可能 35 | - **色見本データの便利さ**: JSONまたはXMLファイルによる色見本データが保存され、ユーザーが簡単にアクセス、編集できます。 36 | - XMLファイルに色見本から読み取ることができる情報が全て乗っているので、色見本データの使い道が幅広くなるでしょう。 37 | 38 | *旧JSONデータ例* 39 | ```JSON 40 | { 41 | "001_01_huai_normal.png": { 42 | "1": { 43 | "hi": [ 44 | 255, 45 | 165, 46 | 0 47 | ], 48 | "normal": [ 49 | 0, 50 | 255, 51 | 165 52 | ], 53 | "shadow": [ 54 | 255, 55 | 165, 56 | 0 57 | ], 58 | "2nd_shadow": [ 59 | 255, 60 | 165, 61 | 0 62 | ] 63 | }, 64 | "2": { 65 | "hi": [ 66 | 0, 67 | 255, 68 | 165 69 | ], 70 | "normal": [ 71 | 0, 72 | 255, 73 | 165 74 | ], 75 | "shadow": [ 76 | 0, 77 | 255, 78 | 0 79 | ], 80 | "2nd_shadow": [ 81 | 255, 82 | 165, 83 | 0 84 | ] 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | *旧XMLデータ例* 91 | ```XML 92 | 93 | 94 | 95 | 96 | 97 | 644 98 | 99 | 100 | 101 | 2196 102 | 103 | 104 | 105 | 2196 106 | 107 | 108 | 109 | 2196 110 | 111 | 112 | 113 | 114 | 115 | 644 116 | 117 | 118 | 119 | 2196 120 | 121 | 122 | 123 | 2196 124 | 125 | 126 | 127 | 2196 128 | 129 | 130 | 131 | 132 | 133 | 134 | ``` 135 | 136 | ### 次のバージョンアップでは、新たなJSONとXMLフォーマットを使用予定です。 137 | ### AEスクリプトはすでに新JSON、XMLと従来のJSON、XML両方に対応済みです。 138 | ### 新JSONはより多くのデータが含まれて、使い道が幅広くなります。 139 | *注意 状況次第では、今後JSONデータを破棄する可能性があり、全面的にXMLデータに移行するかもしれません。* 140 | 141 | *新JSONデータ例* 142 | ```JSON 143 | { 144 | "colorChartData": { 145 | "programVersion": 2.23, 146 | "dataVersion": 1.1, 147 | "markedImgPath": "C:\\this\\is\\a\\sample\\path\\image_marked_colors.png", 148 | "image": { 149 | "name": "image.png", 150 | "path": "C:\\this\\is\\a\\sample\\path\\image.png", 151 | "width": 4000, 152 | "height": 2000, 153 | "gruopNum": 20, 154 | "group": [ 155 | { 156 | "id": 1, 157 | "boxNum": 5, 158 | "validBoxNum": 5, 159 | "orientation": "vertical", 160 | "tag": "", 161 | "groupBoxInfo": { 162 | "boxCordLU": [1000, 50], 163 | "boxCordRD": [1100, 200], 164 | "centerCord": [1050.0, 125.0] 165 | }, 166 | "color": [ 167 | { 168 | "boxID": 1, 169 | "colorType": "hi", 170 | "area": 1000, 171 | "position": [1020.5, 60.0], 172 | "RGB": [255, 0, 0] 173 | }, 174 | { 175 | "boxID": 2, 176 | "colorType": "normal", 177 | "area": 2000, 178 | "position": [1010.5, 100.5], 179 | "RGB": [0, 255, 0] 180 | }, 181 | { 182 | "boxID": 3, 183 | "colorType": "shadow_s_hi", 184 | "area": 300, 185 | "position": [1040.5, 120.0], 186 | "RGB": [0, 0, 255] 187 | }, 188 | { 189 | "boxID": 4, 190 | "colorType": "shadow", 191 | "area": 2000, 192 | "position": [1010.5, 140.5], 193 | "RGB": [255, 255, 0] 194 | }, 195 | { 196 | "boxID": 5, 197 | "colorType": "2nd_shadow", 198 | "area": 2000, 199 | "position": [1010.5, 180.5], 200 | "RGB": [0, 255, 255] 201 | } 202 | ] 203 | } 204 | ] 205 | } 206 | } 207 | } 208 | 209 | 210 | ``` 211 | *新XMLデータ例* 212 | ```XML 213 | 214 | 215 | C:/this/is/a/sample/path/image_marked_colors.png 216 | 217 | 218 | 219 | 220 | 1000 221 | 222 | 223 | 224 | 2000 225 | 226 | 227 | 228 | 300 229 | 230 | 231 | 232 | 2000 233 | 234 | 235 | 236 | 2000 237 | 238 | 239 | 240 | 241 | 242 | 243 | 1000 244 | 245 | 246 | 247 | 2000 248 | 249 | 250 | 251 | 300 252 | 253 | 254 | 255 | 2000 256 | 257 | 258 | 259 | 2000 260 | 261 | 262 | 263 | 264 | 265 | 266 | ``` 267 | 268 | 269 | ## 技術スタック 270 | ### 本プロジェクトは主にPythonで開発され、以下のライブラリおよび技術を含んでいます: 271 | - **画像処理**: Pillow 272 | - **画像分析**: scikit-image 273 | - **インターフェース設計**: Tkinter, ttkthemes 274 | - **データ処理および設定**: JSON, XML (状況次第では、JSONデータは今後破棄する可能性があります。) 275 | ### AEスクリプトはJavaScriptを使用しています。 276 | - JSONとXML色見本データ両方対応可能 277 | - AE内蔵のカラーピッカーツールを使用 278 | - 選択中またはカラーピッカーで選択したカラーを表示でき、どんな色か一目で確認できます 279 | - 汎用かつフリーなプラグイン、OLM Color Keepによる一度最大99色が抽出可能 280 | - **注意:AEスクリプトのみは MITライセンスに準じます。** 281 | 282 | ![AE脚本窗口](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/aescriptmain.png "AE Scriptメインウィンド") 283 | *AEスクリプトのメインウィンド* 284 | 285 | ![AE脚本窗口2](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/aescripttypepanel.png "AE Scriptメインウィンド2") 286 | *タイプパネル:読み込んだ色見本データから、色のタイプをまとめ、自由にほしい色を抽出できます。色見本にすべてのタイプから、若しくは選択中のグループから を選択できます。色見本読み込むにつれ表示します。閉じてもマインウィンドから呼び出し可能* 287 | 288 | 289 | ## 適用シーン 290 | このソフトウェアとスクリプトは、アニメ制作における一般的な色見本が使われる現場 291 | 292 | ## 特性スクリーンショット 293 | 294 | ![ボックス識別](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/usrboxreg.png "ボックス識別") 295 | *デフォルトでサポートされていないボックスなど、作品によって特殊なボックスを切り取り、プリセットとして保存できます。* 296 | 297 | ![ボックス管理作品](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/usrboxmgn.png "ボックス管理") 298 | *プリセットのユーザーボックスを簡単に変更、削除できる管理画面。作品別にカテゴリーで容易に検索できます。* 299 | 300 | ![ボックス多様性](https://github.com/ChenxingM/AutoColorChart/blob/main/supportedBoxes/03_00.PNG "ボックス多様性") 301 | *通常のカラーボックスはもちろん、閾値の設定で、分離したボックスも正しく認識できます。* 302 | 303 | ![カラーピッカー](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/colorpicker.gif "カラーピッカー") 304 | *AEスクリプトUIでは、番号確認するのも面倒、ならカラーピッカーワンクリック!* 305 | 306 | ## 配布物 307 | 308 | - AutoColorChart_2.22.exe (簡体字中国語版) 309 | - AutoColorChart_2.22_JP.exe (日本語版) 310 | - AutoColorChartBeta_CN.jsx (簡体字中国語版) 311 | - AutoColorChartBeta_JP.jsx (日本語版) 312 | - AutoColorChar2_UserGuide.pdf (作成中) 313 | 314 | ## 動作環境(作者検証済) 315 | - **ソフト本体**:Windows 10 316 | - **AEスクリプト**:Adobe After Effect 2023 JavaScriptデバッガーを使用 317 | - **未検証ですが、特に古いでなければ、他のAEとWindowsバーションで動作するはずです。** 318 | - **Mac版は一応テストしてはいるが、大多数の撮影プラグインは未対応のため、お勧めしないし、リリースもしません。需要あれば状況に応じてリリースする可能性あり(Apple Silicon verのみ)** 319 | - **AEスクリプトもまMacでは動作します。** 320 | 321 | ## その他 322 | - Auto Color Chart 1.0のソースコードを公開してます! 323 | - 【Cygames Tech Conference フォローアップ】『プリンセスコネクト!Re:Dive』アニメ撮影におけるテクニカルアーティストの役割~最高のアニメRPGを作るための自動化制作事例~ https://tech.cygames.co.jp/archives/3516/ 324 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | [日本語](README.md) | [简体中文](README.zh.md) | [English](README.en.md) 2 | # 色见本识别程序 及配套AE脚本 3 | # Auto Color Chart 2 4 | 5 | ## 概要 6 | - 本项目是一个桌面应用程序和与其配合使用的AE脚本,旨在将二维动画中的色见本图像转换为**按照色按部件分类(头发,皮肤等),并包含颜色种类(高光,正常等)的色见数据**,以便于摄影时按部件提取颜色。 7 | - 可**自动识别**色见的颜色框,并能判断颜色类型(高光、标准、阴影、二号阴影等),大大提高了后期制作阶段的颜色提取效率。 8 | - 支持单个或批量处理模式,可以快速处理大量的色见。能够检测色见内的重复颜色,并在色见图像上进行标记。 9 | - 通过**Adobe After Effects**脚本,在AE内按部位(如皮肤、头发、衣服等)或按类型(高光、标准等)提取色见本的颜色。 10 | - Cygames已经开发了色见本识别工具,但仅能识别公主链接游戏内动画的色见,并需要准备色见模版。本软件通过独特的算法,能识别更广泛的色见的颜色框,并通过引入预设(用户自定义框)系统,几乎可以识别所有色见本。可应用于各种流程。 11 | 12 | **无需改变现有的制作流程,即可立即使用。** 13 | 14 | ![Auto Color Chart 2 主窗口](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/mian_cn.png "Auto Color Chart 2 主窗口") 15 | *Auto Color Chart 2主窗口* 16 | *演示中使用的色见本已得到公开许可* 17 | 18 | ## 特点 19 | - **颜色框的自动识别**:利用图像处理技术自动识别色见本内的颜色及其类型,并进行分组。 20 | - **阈值识别分离颜色框**:不仅普通颜色框,即使是高光、阴影与标准颜色框分离,也能正确识别。 21 | - **色见本预览**:可以通过鼠标放大、缩小、拖动来检查色见本。 22 | - **忽略色功能**:颜色框应无色,但实际上可能是背景的假色,输入假色至忽略色中,识别时会自动剔除。 23 | - **自定义颜色框管理**:支持特殊的颜色框进行用户自定义和管理,几乎支持所有色见本。 24 | - **设置文件编辑及调试信息窗口**:提供配置文件的编辑功能和调试信息窗口,便于开发者进行故障排除和优化。 25 | - **支持单个或批量处理**:软件支持单个图片或批量模式处理,提高工作效率。 26 | - **重复颜色检测**:能够检测色见本中相同颜色,并进行标记,便于检查。 27 | - **提供AE脚本**:提供Adobe After Effects脚本,根据色见本的颜色在AE中提取指定颜色(全部部位色或单一颜色)。 28 | - AE脚本开源,遵循MIT开源许可协议,可以配合ffx等,实现自动摄影。 29 | - **色见本数据的便利性**:色见数据保存为JSON或XML文件,用户可以轻松访问和编辑。 30 | - XML文件包含了从色见本中读取的所有信息,色见本数据的用途更加广泛。 31 | 32 | 33 | *旧JSON例* 34 | ```JSON 35 | { 36 | "001_01_huai_normal.png": { 37 | "1": { 38 | "hi": [ 39 | 255, 40 | 165, 41 | 0 42 | ], 43 | "normal": [ 44 | 0, 45 | 255, 46 | 165 47 | ], 48 | "shadow": [ 49 | 255, 50 | 165, 51 | 0 52 | ], 53 | "2nd_shadow": [ 54 | 255, 55 | 165, 56 | 0 57 | ] 58 | }, 59 | "2": { 60 | "hi": [ 61 | 0, 62 | 255, 63 | 165 64 | ], 65 | "normal": [ 66 | 0, 67 | 255, 68 | 165 69 | ], 70 | "shadow": [ 71 | 0, 72 | 255, 73 | 0 74 | ], 75 | "2nd_shadow": [ 76 | 255, 77 | 165, 78 | 0 79 | ] 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | *旧XML例* 86 | ```XML 87 | 88 | 89 | 90 | 91 | 92 | 644 93 | 94 | 95 | 96 | 2196 97 | 98 | 99 | 100 | 2196 101 | 102 | 103 | 104 | 2196 105 | 106 | 107 | 108 | 109 | 110 | 644 111 | 112 | 113 | 114 | 2196 115 | 116 | 117 | 118 | 2196 119 | 120 | 121 | 122 | 2196 123 | 124 | 125 | 126 | 127 | 128 | 129 | ``` 130 | 131 | 132 | ### 接下来的版本更新中,将采用新的JSON和XML数据格式。新的JSON比之前拥有更多信息,便于后续的开发和扩展。 133 | ### AE脚本目前已更新,支持新旧JSON和XML 134 | *注意 今后根据情况可能放弃使用JSON数据,全面转向XML* 135 | 136 | *新JSON数据例* 137 | ```JSON 138 | { 139 | "colorChartData": { 140 | "programVersion": 2.23, 141 | "dataVersion": 1.1, 142 | "markedImgPath": "C:\\this\\is\\a\\sample\\path\\image_marked_colors.png", 143 | "image": { 144 | "name": "image.png", 145 | "path": "C:\\this\\is\\a\\sample\\path\\image.png", 146 | "width": 4000, 147 | "height": 2000, 148 | "gruopNum": 20, 149 | "group": [ 150 | { 151 | "id": 1, 152 | "boxNum": 5, 153 | "validBoxNum": 5, 154 | "orientation": "vertical", 155 | "tag": "", 156 | "groupBoxInfo": { 157 | "boxCordLU": [1000, 50], 158 | "boxCordRD": [1100, 200], 159 | "centerCord": [1050.0, 125.0] 160 | }, 161 | "color": [ 162 | { 163 | "boxID": 1, 164 | "colorType": "hi", 165 | "area": 1000, 166 | "position": [1020.5, 60.0], 167 | "RGB": [255, 0, 0] 168 | }, 169 | { 170 | "boxID": 2, 171 | "colorType": "normal", 172 | "area": 2000, 173 | "position": [1010.5, 100.5], 174 | "RGB": [0, 255, 0] 175 | }, 176 | { 177 | "boxID": 3, 178 | "colorType": "shadow_s_hi", 179 | "area": 300, 180 | "position": [1040.5, 120.0], 181 | "RGB": [0, 0, 255] 182 | }, 183 | { 184 | "boxID": 4, 185 | "colorType": "shadow", 186 | "area": 2000, 187 | "position": [1010.5, 140.5], 188 | "RGB": [255, 255, 0] 189 | }, 190 | { 191 | "boxID": 5, 192 | "colorType": "2nd_shadow", 193 | "area": 2000, 194 | "position": [1010.5, 180.5], 195 | "RGB": [0, 255, 255] 196 | } 197 | ] 198 | } 199 | ] 200 | } 201 | } 202 | } 203 | 204 | 205 | ``` 206 | *新XML数据例* 207 | ```XML 208 | ``` 209 | ## 技术栈 210 | ### 本项目主要使用Python开发,包含以下库及技术: 211 | - **图像处理**:Pillow 212 | - **图像分析**:scikit-image 213 | - **界面设计**:Tkinter, ttkthemes 214 | - **数据处理及设置**:JSON, XML(今后根据情况可能放弃使用JSON数据) 215 | ### AE脚本使用JavaScript编写。 216 | - 支持JSON和XML色见本数据 217 | - 使用AE内置的颜色选择工具 218 | - 选中颜色组,吸色后均会显示对应颜色,便于查看 219 | - 取色插件为件OLM Color Keep 220 | - **注意:AE脚本遵循MIT许可证。** 221 | 222 | ![AE脚本主窗口](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/aescriptmain.png "AE脚本主窗口") 223 | 224 | ![AE脚本窗口2](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/aescripttypepanel.png "AE脚本窗口2") 225 | 226 | ## 适用场景 227 | 该软件和脚本适用于拥有动画制作中常用色见本的流程 228 | 229 | ## 特性截图 230 | 231 | ![颜色框识别](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/usrboxreg.png "颜色框识别") 232 | 233 | ![颜色框管理](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/usrboxmgn.png "颜色框管理") 234 | 235 | ![颜色框多样性](https://github.com/ChenxingM/AutoColorChart/blob/main/supportedBoxes/03_00.PNG "颜色框多样性") 236 | 237 | ![颜色选择器](https://github.com/ChenxingM/AutoColorChart/blob/main/screenshoots/colorpicker.gif "颜色选择器") 238 | 239 | ## 发布 240 | 请从Release下载。 241 | - AutoColorChart_2.22.exe (简体中文) 242 | - AutoColorChart_2.22_JP.exe (日语) 243 | - AutoColorChartBeta_CN.jsx (简体中文AE脚本) 244 | - AutoColorChartBeta_JP.jsx (日语AE脚本) 245 | - AutoColorChar2_UserGuide.pdf (制作中) 246 | 247 | ## 软件环境(作者验证过的) 248 | - **软件本体**:Windows 10 249 | - **AE脚本**:Adobe After Effect 2023 使用JavaScript调试器 250 | - **虽未验证,只要不是特别老的版本,其他AE和Windows版本应该也能运行。** 251 | - **Mac版已进行一定测试,但由于大多数摄影插件不支持,不推荐使用,也不发布。如有需求,视情况可能发布Apple Silicon版本。** 252 | - **AE脚本在Mac上也能运行。** 253 | 254 | ## 其他 255 | - Auto Color Chart 1.0的源代码已发布! 256 | - CY技术大会文章链接: [【Cygames Tech Conference フォローアップ】『プリンセスコネクト!Re:Dive』アニメ撮影におけるテクニカルアーティストの役割~最高のアニメRPGを作るための自動化制作事例~](https://tech.cygames.co.jp/archives/3516/) 257 | -------------------------------------------------------------------------------- /_acc1.0/AutoColorChart.py: -------------------------------------------------------------------------------- 1 | import json 2 | from PIL import Image, ImageDraw, ImageFont 3 | from skimage.measure import label, regionprops 4 | import numpy as np 5 | import random 6 | import os 7 | 8 | # 颜色框的线颜色 9 | box_color_main = (243, 24, 133) 10 | #box_color_main = (255, 0, 255) 11 | #box_color_sec = (0, 0, 255) 12 | # box_color_main = (250, 0, 142) 13 | # box_color_sec = (243, 24, 133) 14 | box_color_sec = (22, 47, 204) 15 | # box_color_sec = (0, 12, 250) 16 | 17 | line_width = 3 18 | # 需要提取的颜色 19 | colors_to_extract = [box_color_main, box_color_sec] 20 | 21 | input_img = "normal/001_002_02.png" 22 | 23 | line_width -= 1 24 | def extract_colors(input_file, output_file, colors): 25 | # 提取指定颜色的像素点 26 | image = Image.open(input_file).convert("RGBA") 27 | width, height = image.size 28 | new_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) 29 | 30 | for y in range(height): 31 | for x in range(width): 32 | current_color = image.getpixel((x, y)) 33 | if current_color[:3] in colors: 34 | new_image.putpixel((x, y), current_color) 35 | new_image.save(output_file) 36 | 37 | 38 | def replace_color(image_path, colors_to_replace, new_color): 39 | img = Image.open(image_path) 40 | img = img.convert("RGBA") 41 | 42 | d = img.getdata() 43 | 44 | new_image = [] 45 | for item in d: 46 | # 如果像素是完全透明的,就跳过它 47 | if item[3] == 0: 48 | new_image.append(item) 49 | continue 50 | 51 | # 如果不透明,并且颜色在要替换的列表里,就替换颜色 52 | if item[:3] in colors_to_replace: 53 | new_image.append(new_color + (item[3],)) # 保持原始的 alpha 值 54 | else: 55 | new_image.append(item) 56 | 57 | img.putdata(new_image) 58 | img.save("_output.png") 59 | 60 | 61 | def find_box_mark_colors(input_file, output_file, original_file, target_color): 62 | # 查找标记颜色框的位置和颜色 63 | image = Image.open(input_file).convert("RGBA") 64 | width, height = image.size 65 | new_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) 66 | original_image0 = Image.open(original_file).convert("RGBA") 67 | original_image = Image.open(original_file).convert("RGB") 68 | height, width, _ = np.array(image).shape 69 | foreground = np.all(np.array(image)[:, :, :3] != target_color, axis=-1) 70 | 71 | labels = label(foreground) 72 | draw = ImageDraw.Draw(image) 73 | drawN = ImageDraw.Draw(new_image) 74 | # fontT = "PingFang.ttc" 75 | # font = ImageFont.truetype(fontT, 15) 76 | 77 | rectangles = [] 78 | center_colors = [] 79 | for region in regionprops(labels): 80 | center = region.centroid 81 | color_center = original_image0.getpixel((int(center[1]), int(center[0]))) 82 | if color_center != (255, 255, 255, 255) and color_center != (0, 0, 0, 0): 83 | draw.ellipse((center[1] + 35, center[0] - 10, center[1] + 50, center[0] + 5), fill=color_center) 84 | center_colors.append(color_center) 85 | # 寻找四个方向上的边界坐标 86 | directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # 上、下、左、右 87 | boundary_coordinates = [] 88 | 89 | for dx, dy in directions: 90 | x, y = int(center[1]), int(center[0]) 91 | while 0 <= x < width and 0 <= y < height: 92 | current_color = image.getpixel((x, y)) 93 | if current_color[:3] == target_color: 94 | boundary_coordinates.append((x, y)) 95 | break 96 | x += dx 97 | y += dy 98 | 99 | 100 | # 寻找通过边界坐标的矩形 101 | min_x = min(coord[0] for coord in boundary_coordinates) 102 | max_x = max(coord[0] for coord in boundary_coordinates) 103 | min_y = min(coord[1] for coord in boundary_coordinates) 104 | max_y = max(coord[1] for coord in boundary_coordinates) 105 | 106 | # 绘制并填充矩形的随机颜色 107 | # draw.rectangle([(min_x, min_y), (max_x, max_y)], outline="blue", fill=random_color1) 108 | 109 | # 存储矩形的详细信息 110 | rectangles.append(((min_x, min_y), (max_x, max_y), center, color_center)) 111 | 112 | box_names = [] 113 | color_match = {} 114 | 115 | # 重新绘制矩形和中心点,如果中心点位于矩形内部 116 | for rectangle in rectangles: 117 | (min_x, min_y), (max_x, max_y), center, color_center = rectangle 118 | 119 | # 如果中心点位于矩形内部 120 | if min_x <= center[1] <= max_x and min_y <= center[0] <= max_y: 121 | 122 | # 标记中心点 123 | draw.point((center[1], center[0]), fill="red") 124 | 125 | # 标记矩形顶点 126 | vertices = [(min_x - 1, min_y - 1), (max_x + 1, min_y - 1), (min_x - 1, max_y + 1), 127 | (max_x + 1, max_y + 1)] 128 | for vertex in vertices: 129 | random_color3 = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 130 | draw.point(vertex, fill=random_color3) 131 | 132 | # 矩形的坐标 133 | top_left, top_right, bottom_left, bottom_right = (min_x - line_width, min_y - line_width), (max_x + line_width, min_y - line_width), ( 134 | min_x - line_width, max_y + line_width), (max_x + line_width, max_y + line_width) 135 | 136 | top_left = (top_left[0], top_left[1] - 1) 137 | top_right = (top_right[0], top_right[1] - 1) 138 | bottom_left = (bottom_left[0], bottom_left[1] + 1) 139 | bottom_right = (bottom_right[0], bottom_right[1] + 1) 140 | 141 | if (original_image.getpixel((top_left[0], top_left[1])) != box_color_main and original_image.getpixel( 142 | (top_right[0], top_right[1])) == box_color_main and original_image.getpixel( 143 | (bottom_left[0], bottom_left[1])) == box_color_main and original_image.getpixel( 144 | (bottom_right[0], bottom_right[1])) == box_color_main): 145 | box_name = "normal" 146 | 147 | elif original_image.getpixel((top_left[0], top_left[1])) != box_color_main and original_image.getpixel( 148 | (top_right[0], top_right[1])) != box_color_main and original_image.getpixel( 149 | (bottom_left[0], bottom_left[1])) != box_color_main and original_image.getpixel( 150 | (bottom_right[0], bottom_right[1])) == box_color_main: 151 | box_name = "hi" 152 | 153 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_main and original_image.getpixel( 154 | (top_right[0], top_right[1])) == box_color_main and original_image.getpixel( 155 | (bottom_left[0], bottom_left[1])) == box_color_main and original_image.getpixel( 156 | (bottom_right[0], bottom_right[1])) == box_color_main: 157 | box_name = "shadow" 158 | 159 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_main and original_image.getpixel( 160 | (top_right[0], top_right[1])) == box_color_main and original_image.getpixel( 161 | (bottom_left[0], bottom_left[1])) != box_color_main and original_image.getpixel( 162 | (bottom_right[0], bottom_right[1])) != box_color_main: 163 | box_name = "2nd_shadow" 164 | 165 | elif (original_image.getpixel((top_left[0], top_left[1])) != box_color_sec and original_image.getpixel( 166 | (top_right[0], top_right[1])) == box_color_sec and original_image.getpixel( 167 | (bottom_left[0], bottom_left[1])) == box_color_sec and original_image.getpixel( 168 | (bottom_right[0], bottom_right[1])) == box_color_sec): 169 | box_name = "normal" 170 | 171 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_sec and original_image.getpixel( 172 | (top_right[0], top_right[1])) == box_color_sec and original_image.getpixel( 173 | (bottom_left[0], bottom_left[1])) == box_color_sec and original_image.getpixel( 174 | (bottom_right[0], bottom_right[1])) == box_color_sec: 175 | box_name = "shadow" 176 | 177 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_sec and original_image.getpixel( 178 | (top_right[0], top_right[1])) == box_color_sec and original_image.getpixel( 179 | (bottom_left[0], bottom_left[1])) != box_color_sec and original_image.getpixel( 180 | (bottom_right[0], bottom_right[1])) != box_color_sec: 181 | box_name = "2nd_shadow" 182 | 183 | elif original_image.getpixel((top_left[0], top_left[1])) != box_color_sec and original_image.getpixel( 184 | (top_right[0], top_right[1])) != box_color_sec and original_image.getpixel( 185 | (bottom_left[0], bottom_left[1])) != box_color_sec and original_image.getpixel( 186 | (bottom_right[0], bottom_right[1])) == box_color_sec: 187 | box_name = "hi" 188 | 189 | else: 190 | box_name = "unknown" 191 | 192 | if color_center != (255, 255, 255, 255): 193 | draw.text((center[1] + 52, center[0] - 10), box_name, fill="black") 194 | draw.text((center[1] + 50, center[0]), str(color_center), fill=color_center) 195 | 196 | box_names.append(box_name) 197 | color_match[center] = (box_name, color_center) 198 | # print(color_match) 199 | 200 | # 检查框是否相邻并分组 201 | def is_adjacent(rectangle, rectangle_list, x_threshold): 202 | ((min_x1, min_y1), (max_x1, max_y1), center1, color_center1) = rectangle 203 | for rectangle2 in rectangle_list: 204 | ((min_x2, min_y2), (max_x2, max_y2), center2, color_center2) = rectangle2 205 | # 计算x坐标的差值 206 | x_min_diff = abs(min_x1 - min_x2) 207 | x_max_diff = abs(max_x1 - max_x2) 208 | x1_diff = abs(min_x1 - max_x2) 209 | x2_diff = abs(max_x1 - min_x2) 210 | if (abs((max_y1 + line_width) - (min_y2 - line_width)) == line_width or abs( 211 | (max_y2 + line_width) - (min_y1 - line_width)) == line_width) and x_min_diff <= x_threshold and x_max_diff < x_threshold: 212 | return True 213 | elif (abs((min_y1 + line_width) - (min_y2 - line_width)) < 10 or abs((min_y2 + line_width) - (min_y1 - line_width)) < 10) and (x1_diff < 10 or x2_diff < 10): 214 | 215 | return True 216 | return False 217 | 218 | # (min_x, min_y):左上角的顶点,也就是x坐标和y坐标都最小的点。 219 | # (max_x, min_y):右上角的顶点,也就是x坐标最大,y坐标最小的点。 220 | # (min_x, max_y):左下角的顶点,也就是x坐标最小,y坐标最大的点。 221 | # (max_x, max_y):右下角的顶点,也就是x坐标和y坐标都最大的点。 222 | 223 | def group_adjacent(rectangles): 224 | groups = [] 225 | while rectangles: # 循环直到所有矩形都被分组 226 | rectangle = rectangles.pop(0) # 取出第一个矩形 227 | found_group = False 228 | for group in groups: 229 | if is_adjacent(rectangle, group, 40): 230 | group.append(rectangle) 231 | found_group = True 232 | break 233 | if not found_group: 234 | groups.append([rectangle]) # 以该矩形开始一个新的分组 235 | return groups 236 | 237 | box_groups = group_adjacent(rectangles) 238 | group = {} 239 | 240 | for i, box_group in enumerate(box_groups): 241 | group_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 242 | for rectangle in box_group: 243 | ((min_x, min_y), (max_x, max_y), center, color_center) = rectangle 244 | drawN.rectangle([(min_x, min_y), (max_x, max_y)], outline=group_color, fill=group_color) 245 | # 在矩形中心添加分组编号 246 | group_number = str(i) 247 | if i != 0: 248 | draw.text((min_x, min_y), group_number, fill='black') 249 | group[center] = (group_number, color_center) 250 | 251 | # 保存图像 252 | image.save(output_file) 253 | # new_image.save("colorRange.png") 254 | os.remove("_output.png") 255 | 256 | return group, color_match 257 | 258 | 259 | extract_colors(input_img, "_output.png", colors_to_extract) 260 | replace_color("_output.png", [box_color_sec, box_color_main], (243, 24, 133)) 261 | 262 | group, color_match = find_box_mark_colors("_output.png", "output_marked.png", input_img, (243, 24, 133)) 263 | # print(group) 264 | common_keys = set(group.keys()) & set(color_match.keys()) 265 | for key in common_keys: 266 | color_match[key] = (color_match[key], group[key][0]) 267 | 268 | _json_color_chart = {} 269 | for key, value in color_match.items(): 270 | group = value[0][0] 271 | color = value[0][1] 272 | label = value[1] 273 | 274 | if label not in _json_color_chart: 275 | _json_color_chart[label] = {} 276 | 277 | _json_color_chart[label][group] = color 278 | 279 | # 如果一个组中只有两个元素,且其中包含 'normal' 和'2nd_shadow',将'2nd_shadow'替换为 'shadow' 280 | for group, colors in _json_color_chart.items(): 281 | if len(colors) == 1 and 'unknown' in colors: 282 | colors['normal'] = colors.pop('unknown') 283 | if len(colors) == 2 and 'unknown' in colors and '2nd_shadow' in colors: 284 | colors['normal'] = colors.pop('unknown') 285 | colors['shadow'] = colors.pop('2nd_shadow') 286 | if len(colors) == 3 and 'hi' in colors and 'normal' in colors and '2nd_shadow' in colors: 287 | colors['shadow'] = colors.pop('2nd_shadow') 288 | if len(colors) == 3 and 'unknown' in colors and 'normal' in colors and '2nd_shadow' in colors: 289 | colors['hi'] = colors.pop('unknown') 290 | colors['normal'] = colors.pop('normal') 291 | colors['2nd_shadow'] = colors.pop('shadow') 292 | if len(colors) == 3 and 'unknown' in colors and 'shadow' in colors and '2nd_shadow' in colors: 293 | colors['normal'] = colors.pop('unknown') 294 | colors['shadow'] = colors.pop('shadow') 295 | colors['2nd_shadow'] = colors.pop('2nd_shadow') 296 | 297 | if len(colors) == 4 and 'hi' in colors and 'normal' in colors and 'shadow' in colors and 'unknown' in colors: 298 | colors['2nd_shadow'] = colors.pop('unknown') 299 | if len(colors) == 4 and 'unknown' in colors and 'normal' in colors and 'shadow' in colors and '2nd_shadow' in colors: 300 | colors['hi'] = colors.pop('unknown') 301 | colors['normal'] = colors.pop('normal') 302 | colors['shadow'] = colors.pop('shadow') 303 | colors['2nd_shadow'] = colors.pop('2nd_shadow') 304 | if len(colors) == 4 and 'unknown' in colors and 'normal' in colors and 'shadow' in colors and '2nd_shadow' in colors: 305 | colors['hi'] = colors.pop('unknown') 306 | colors['normal'] = colors.pop('normal') 307 | colors['shadow'] = colors.pop('shadow') 308 | colors['2nd_shadow'] = colors.pop('2nd_shadow') 309 | 310 | json_color_chart = {} 311 | img_str = os.path.basename(input_img) 312 | json_color_chart[str(img_str)] = _json_color_chart 313 | 314 | new_json_color_chart = {} 315 | for image_name, color_data in json_color_chart.items(): 316 | new_color_data = {} 317 | for key, value in color_data.items(): 318 | if not isinstance(key, tuple): 319 | new_color_data[key] = value 320 | new_json_color_chart[image_name] = new_color_data 321 | 322 | # print(new_json_color_chart) 323 | 324 | """" 325 | with open('encrypted_data.cta', 'w') as file: 326 | file.write(encrypted_data) 327 | """ 328 | fileName = 'data/color_chart_' 329 | file_name = fileName + str(img_str) 330 | json_file_name = file_name + '.json' 331 | with open(json_file_name, 'w', encoding='utf-8') as f: 332 | json.dump(new_json_color_chart, f, ensure_ascii=True) 333 | 334 | -------------------------------------------------------------------------------- /_acc1.0/AutoColorChartGUI.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import filedialog, messagebox 3 | import json 4 | from PIL import Image, ImageDraw, ImageTk 5 | from skimage.measure import label, regionprops 6 | import numpy as np 7 | import random 8 | import os 9 | 10 | def extract_colors(input_file, output_file, colors): 11 | # 提取指定颜色的像素点 12 | image = Image.open(input_file).convert("RGBA") 13 | width, height = image.size 14 | new_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) 15 | 16 | for y in range(height): 17 | for x in range(width): 18 | current_color = image.getpixel((x, y)) 19 | if current_color[:3] in colors: 20 | new_image.putpixel((x, y), current_color) 21 | new_image.save(output_file) 22 | 23 | 24 | def replace_color(image_path, colors_to_replace, new_color): 25 | img = Image.open(image_path) 26 | img = img.convert("RGBA") 27 | 28 | d = img.getdata() 29 | 30 | new_image = [] 31 | for item in d: 32 | # 如果像素是完全透明的,就跳过它 33 | if item[3] == 0: 34 | new_image.append(item) 35 | continue 36 | 37 | # 如果不透明,并且颜色在要替换的列表里,就替换颜色 38 | if item[:3] in colors_to_replace: 39 | new_image.append(new_color + (item[3],)) # 保持原始的 alpha 值 40 | else: 41 | new_image.append(item) 42 | 43 | img.putdata(new_image) 44 | img.save("_output.png") 45 | 46 | 47 | def find_box_mark_colors(input_file, output_file, original_file, target_color, box_color_main, box_color_sec): 48 | # 查找标记颜色框的位置和颜色 49 | image = Image.open(input_file).convert("RGBA") 50 | width, height = image.size 51 | new_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) 52 | original_image0 = Image.open(original_file).convert("RGBA") 53 | original_image = Image.open(original_file).convert("RGB") 54 | height, width, _ = np.array(image).shape 55 | foreground = np.all(np.array(image)[:, :, :3] != target_color, axis=-1) 56 | 57 | labels = label(foreground) 58 | 59 | draw = ImageDraw.Draw(image) 60 | drawN = ImageDraw.Draw(new_image) 61 | # fontT = "PingFang.ttc" 62 | # font = ImageFont.truetype(fontT, 15) 63 | 64 | rectangles = [] 65 | center_colors = [] 66 | for region in regionprops(labels): 67 | center = region.centroid 68 | color_center = original_image0.getpixel((int(center[1]), int(center[0]))) 69 | if color_center != (255, 255, 255, 255): 70 | draw.ellipse((center[1] + 35, center[0] - 10, center[1] + 50, center[0] + 5), fill=color_center) 71 | center_colors.append(color_center) 72 | # 寻找四个方向上的边界坐标 73 | directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # 上、下、左、右 74 | boundary_coordinates = [] 75 | for dx, dy in directions: 76 | x, y = int(center[1]), int(center[0]) 77 | while 0 <= x < width and 0 <= y < height: 78 | current_color = image.getpixel((x, y)) 79 | if current_color[:3] == target_color: 80 | boundary_coordinates.append((x, y)) 81 | break 82 | x += dx 83 | y += dy 84 | 85 | # 寻找通过边界坐标的矩形 86 | min_x = min(coord[0] for coord in boundary_coordinates) 87 | max_x = max(coord[0] for coord in boundary_coordinates) 88 | min_y = min(coord[1] for coord in boundary_coordinates) 89 | max_y = max(coord[1] for coord in boundary_coordinates) 90 | 91 | # 绘制并填充矩形的随机颜色 92 | # draw.rectangle([(min_x, min_y), (max_x, max_y)], outline="blue", fill=random_color1) 93 | 94 | # 存储矩形的详细信息 95 | rectangles.append(((min_x, min_y), (max_x, max_y), center, color_center)) 96 | 97 | box_names = [] 98 | color_match = {} 99 | 100 | # 重新绘制矩形和中心点,如果中心点位于矩形内部 101 | for rectangle in rectangles: 102 | (min_x, min_y), (max_x, max_y), center, color_center = rectangle 103 | 104 | # 如果中心点位于矩形内部 105 | if min_x <= center[1] <= max_x and min_y <= center[0] <= max_y: 106 | 107 | # 标记中心点 108 | draw.point((center[1], center[0]), fill="red") 109 | 110 | # 标记矩形顶点 111 | vertices = [(min_x - 1, min_y - 1), (max_x + 1, min_y - 1), (min_x - 1, max_y + 1), 112 | (max_x + 1, max_y + 1)] 113 | for vertex in vertices: 114 | random_color3 = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 115 | draw.point(vertex, fill=random_color3) 116 | 117 | # 矩形的坐标 118 | top_left, top_right, bottom_left, bottom_right = (min_x - 1, min_y - 1), (max_x + 1, min_y - 1), ( 119 | min_x - 1, max_y + 1), (max_x + 1, max_y + 1) 120 | 121 | top_left = (top_left[0], top_left[1] - 1) 122 | top_right = (top_right[0], top_right[1] - 1) 123 | bottom_left = (bottom_left[0], bottom_left[1] + 1) 124 | bottom_right = (bottom_right[0], bottom_right[1] + 1) 125 | 126 | if (original_image.getpixel((top_left[0], top_left[1])) != box_color_main and original_image.getpixel( 127 | (top_right[0], top_right[1])) == box_color_main and original_image.getpixel( 128 | (bottom_left[0], bottom_left[1])) == box_color_main and original_image.getpixel( 129 | (bottom_right[0], bottom_right[1])) == box_color_main): 130 | box_name = "normal" 131 | 132 | elif original_image.getpixel((top_left[0], top_left[1])) != box_color_main and original_image.getpixel( 133 | (top_right[0], top_right[1])) != box_color_main and original_image.getpixel( 134 | (bottom_left[0], bottom_left[1])) != box_color_main and original_image.getpixel( 135 | (bottom_right[0], bottom_right[1])) == box_color_main: 136 | box_name = "hi" 137 | 138 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_main and original_image.getpixel( 139 | (top_right[0], top_right[1])) == box_color_main and original_image.getpixel( 140 | (bottom_left[0], bottom_left[1])) == box_color_main and original_image.getpixel( 141 | (bottom_right[0], bottom_right[1])) == box_color_main: 142 | box_name = "shadow" 143 | 144 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_main and original_image.getpixel( 145 | (top_right[0], top_right[1])) == box_color_main and original_image.getpixel( 146 | (bottom_left[0], bottom_left[1])) != box_color_main and original_image.getpixel( 147 | (bottom_right[0], bottom_right[1])) != box_color_main: 148 | box_name = "2nd_shadow" 149 | 150 | elif (original_image.getpixel((top_left[0], top_left[1])) != box_color_sec and original_image.getpixel( 151 | (top_right[0], top_right[1])) == box_color_sec and original_image.getpixel( 152 | (bottom_left[0], bottom_left[1])) == box_color_sec and original_image.getpixel( 153 | (bottom_right[0], bottom_right[1])) == box_color_sec): 154 | box_name = "normal" 155 | 156 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_sec and original_image.getpixel( 157 | (top_right[0], top_right[1])) == box_color_sec and original_image.getpixel( 158 | (bottom_left[0], bottom_left[1])) == box_color_sec and original_image.getpixel( 159 | (bottom_right[0], bottom_right[1])) == box_color_sec: 160 | box_name = "shadow" 161 | 162 | elif original_image.getpixel((top_left[0], top_left[1])) == box_color_sec and original_image.getpixel( 163 | (top_right[0], top_right[1])) == box_color_sec and original_image.getpixel( 164 | (bottom_left[0], bottom_left[1])) != box_color_sec and original_image.getpixel( 165 | (bottom_right[0], bottom_right[1])) != box_color_sec: 166 | box_name = "2nd_shadow" 167 | 168 | elif original_image.getpixel((top_left[0], top_left[1])) != box_color_sec and original_image.getpixel( 169 | (top_right[0], top_right[1])) != box_color_sec and original_image.getpixel( 170 | (bottom_left[0], bottom_left[1])) != box_color_sec and original_image.getpixel( 171 | (bottom_right[0], bottom_right[1])) == box_color_sec: 172 | box_name = "hi" 173 | 174 | else: 175 | box_name = "unknown" 176 | 177 | if color_center != (255, 255, 255, 255): 178 | draw.text((center[1] + 52, center[0] - 10), box_name, fill="black") 179 | draw.text((center[1] + 50, center[0]), str(color_center), fill=color_center) 180 | 181 | box_names.append(box_name) 182 | color_match[center] = (box_name, color_center) 183 | # print(color_match) 184 | 185 | # 检查框是否相邻并分组 186 | def is_adjacent(rectangle, rectangle_list, x_threshold): 187 | ((min_x1, min_y1), (max_x1, max_y1), center1, color_center1) = rectangle 188 | for rectangle2 in rectangle_list: 189 | ((min_x2, min_y2), (max_x2, max_y2), center2, color_center2) = rectangle2 190 | # 计算x坐标的差值 191 | x_min_diff = abs(min_x1 - min_x2) 192 | x_max_diff = abs(max_x1 - max_x2) 193 | if (abs((max_y1 + 1) - (min_y2 - 1)) == 1 or abs( 194 | (max_y2 + 1) - (min_y1 - 1)) == 1) and x_min_diff <= x_threshold and x_max_diff < x_threshold: 195 | return True 196 | return False 197 | 198 | def group_adjacent(rectangles): 199 | groups = [] 200 | while rectangles: # 循环直到所有矩形都被分组 201 | rectangle = rectangles.pop(0) # 取出第一个矩形 202 | found_group = False 203 | for group in groups: 204 | if is_adjacent(rectangle, group, 70): 205 | group.append(rectangle) 206 | found_group = True 207 | break 208 | if not found_group: 209 | groups.append([rectangle]) # 以该矩形开始一个新的分组 210 | return groups 211 | 212 | box_groups = group_adjacent(rectangles) 213 | group = {} 214 | 215 | for i, box_group in enumerate(box_groups): 216 | group_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 217 | for rectangle in box_group: 218 | ((min_x, min_y), (max_x, max_y), center, color_center) = rectangle 219 | drawN.rectangle([(min_x, min_y), (max_x, max_y)], outline=group_color, fill=group_color) 220 | # 在矩形中心添加分组编号 221 | group_number = str(i) 222 | if i != 0: 223 | draw.text((min_x, min_y), group_number, fill='black') 224 | group[center] = (group_number, color_center) 225 | 226 | 227 | 228 | # 保存图像 229 | #image.save(output_file) 230 | #new_image.save("colorRange.png") 231 | os.remove("_output.png") 232 | 233 | return group, color_match 234 | 235 | 236 | class Application(tk.Tk): 237 | def __init__(self): 238 | super().__init__() 239 | self.save_path = None 240 | self.title("Auto Color Chart") 241 | self.geometry('320x150') 242 | self.minsize(width=320, height=150) 243 | 244 | 245 | self.input_img = "" 246 | self.output_img = "" 247 | self.box_color_main = "" 248 | self.box_color_sec = "" 249 | 250 | self.lbl_box_color_main = tk.Label(self, text="Color Box 1:") 251 | self.lbl_box_color_main.place(x=20, y=20) 252 | self.entry_box_color_main = tk.Entry(self) 253 | self.entry_box_color_main.place(x=100, y=20, width=190) 254 | 255 | self.lbl_box_color_sec = tk.Label(self, text="Color Box 2:") 256 | self.lbl_box_color_sec.place(x=20, y=60) 257 | self.entry_box_color_sec = tk.Entry(self) 258 | self.entry_box_color_sec.place(x=100, y=60, width=190) 259 | 260 | self.btn_open = tk.Button(self, text="Open Image", command=self.open_image, width=6) 261 | self.btn_open.place(x=10, y=100) 262 | 263 | self.btn_select_path = tk.Button(self, text="Save Path", command=self.select_path, width=6) 264 | self.btn_select_path.place(x=100, y=100) 265 | 266 | self.btn_process = tk.Button(self, text="Process Image", command=self.process_image, state=tk.DISABLED, 267 | width=9) 268 | self.btn_process.place(x=190, y=100) 269 | def open_image(self): 270 | self.input_img = filedialog.askopenfilename() 271 | if self.input_img: 272 | self.output_img = self.input_img.replace(".png", "_marked.png") 273 | 274 | img = Image.open(self.input_img) 275 | img.thumbnail((300, 300)) 276 | img = ImageTk.PhotoImage(img) 277 | 278 | self.lbl_image = tk.Label(self, image=img) 279 | self.lbl_image.image = img 280 | self.lbl_image.place(x=0, y=150) 281 | self.minsize(width=320, height=360) 282 | self.maxsize(width=320, height=360) 283 | 284 | 285 | self.btn_process.config(state=tk.NORMAL) 286 | 287 | def process_image(self): 288 | self.box_color_main = self.entry_box_color_main.get() 289 | self.box_color_sec = self.entry_box_color_sec.get() 290 | 291 | try: 292 | self.box_color_main = eval(self.box_color_main) 293 | self.box_color_sec = eval(self.box_color_sec) 294 | 295 | except: 296 | messagebox.showerror("Error", "Invalid color values.") 297 | return 298 | 299 | extract_colors(self.input_img, "_output.png", [self.box_color_main, self.box_color_sec]) 300 | replace_color("_output.png", [self.box_color_sec, self.box_color_main],(243, 24, 133)) 301 | 302 | group, color_match = find_box_mark_colors("_output.png", self.output_img, self.input_img, (243, 24, 133), self.box_color_main, self.box_color_sec) 303 | json_file_name = self.save_color_chart(group, color_match) 304 | 305 | messagebox.showinfo("Color Chart Saved", "Color chart saved as " + json_file_name) 306 | 307 | self.btn_process.config(state=tk.DISABLED) 308 | self.input_img = "" 309 | self.output_img = "" 310 | self.box_color_main = "" 311 | self.box_color_sec = "" 312 | 313 | def select_path(self): 314 | self.save_path = filedialog.askdirectory() 315 | def save_color_chart(self, group, color_match): 316 | common_keys = set(group.keys()) & set(color_match.keys()) 317 | for key in common_keys: 318 | color_match[key] = (color_match[key], group[key][0]) 319 | 320 | _json_color_chart = {} 321 | for key, value in color_match.items(): 322 | group = value[0][0] 323 | color = value[0][1] 324 | label = value[1] 325 | 326 | if label not in _json_color_chart: 327 | _json_color_chart[label] = {} 328 | 329 | _json_color_chart[label][group] = color 330 | 331 | # 如果一个组中只有两个元素,且其中包含 'normal' 和'2nd_shadow',将'2nd_shadow'替换为 'shadow' 332 | for group, colors in _json_color_chart.items(): 333 | if len(colors) == 1 and 'unknown' in colors: 334 | colors['normal'] = colors.pop('unknown') 335 | if len(colors) == 2 and 'unknown' in colors and '2nd_shadow' in colors: 336 | colors['normal'] = colors.pop('unknown') 337 | colors['shadow'] = colors.pop('2nd_shadow') 338 | if len(colors) == 3 and 'hi' in colors and 'normal' in colors and '2nd_shadow' in colors: 339 | colors['shadow'] = colors.pop('2nd_shadow') 340 | if len(colors) == 3 and 'unknown' in colors and 'shadow' in colors and '2nd_shadow' in colors: 341 | colors['normal'] = colors.pop('unknown') 342 | colors['shadow'] = colors.pop('shadow') 343 | colors['2nd_shadow'] = colors.pop('2nd_shadow') 344 | if len(colors) == 3 and 'unknown' in colors and 'normal' in colors and '2nd_shadow' in colors: 345 | colors['hi'] = colors.pop('unknown') 346 | colors['normal'] = colors.pop('normal') 347 | colors['2nd_shadow'] = colors.pop('shadow') 348 | if len(colors) == 4 and 'hi' in colors and 'normal' in colors and 'shadow' in colors and 'unknown' in colors: 349 | colors['2nd_shadow'] = colors.pop('unknown') 350 | if len(colors) == 4 and 'unknown' in colors and 'normal' in colors and 'shadow' in colors and '2nd_shadow' in colors: 351 | colors['hi'] = colors.pop('unknown') 352 | colors['normal'] = colors.pop('normal') 353 | colors['shadow'] = colors.pop('shadow') 354 | colors['2nd_shadow'] = colors.pop('2nd_shadow') 355 | 356 | json_color_chart = {} 357 | img_str = os.path.basename(self.input_img) 358 | json_color_chart[str(img_str)] = _json_color_chart 359 | 360 | new_json_color_chart = {} 361 | for image_name, color_data in json_color_chart.items(): 362 | new_color_data = {} 363 | for key, value in color_data.items(): 364 | if not isinstance(key, tuple): 365 | new_color_data[key] = value 366 | new_json_color_chart[image_name] = new_color_data 367 | 368 | # print(new_json_color_chart) 369 | fileName = 'color_chart_' 370 | file_name = fileName + str(img_str) 371 | json_file_name = file_name + '.json' 372 | with open(os.path.join(self.save_path, json_file_name), 'w', encoding='utf-8') as f: 373 | json.dump(new_json_color_chart, f, ensure_ascii=True) 374 | return os.path.join(self.save_path, json_file_name) 375 | 376 | 377 | if __name__ == "__main__": 378 | app = Application() 379 | app.mainloop() 380 | -------------------------------------------------------------------------------- /sampleDatas/color_chart_sample_boxes.png.json: -------------------------------------------------------------------------------- 1 | { /*注意!本结果仅供提供的示例框样本的识别结果参考,该颜色不具有代表性,实际结果中的颜色不带有alpha通道*/ 2 | "sample_boxes.png": { 3 | "1": { 4 | "hi": [128, 128, 128], 5 | "normal": [128, 128, 128], 6 | "shadow": [128, 128, 128], 7 | "2nd_shadow": [128, 128, 128] 8 | }, 9 | "2": { 10 | "hi": [128, 128, 128], 11 | "normal": [128, 128, 128], 12 | "shadow": [128, 128, 128] 13 | }, 14 | "3": { 15 | "normal": [128, 128, 128], 16 | "shadow": [128, 128, 128], 17 | "2nd_shadow": [128, 128, 128] 18 | }, 19 | "4": { 20 | "normal": [128, 128, 128], 21 | "shadow": [128, 128, 128] 22 | }, 23 | "5": { 24 | "hi": [128, 128, 128], 25 | "hi_in_shadow": [128, 128, 128], 26 | "normal": [128, 128, 128], 27 | "shadow": [128, 128, 128], 28 | "2nd_shadow": [128, 128, 128] 29 | }, 30 | "6": { 31 | "hi": [128, 128, 128], 32 | "normal": [166, 200, 195, 0], 33 | "shadow_s_hi": [128, 128, 128], 34 | "shadow": [128, 128, 128], 35 | "2nd_shadow_hi": [128, 128, 128], 36 | "2nd_shadow": [128, 128, 128], 37 | "normal_tp": [128, 128, 128] 38 | }, 39 | "7": { 40 | "hi": [128, 128, 128], 41 | "hi_in_shadow": [128, 128, 128], 42 | "normal": [128, 128, 128], 43 | "shadow": [128, 128, 128] 44 | }, 45 | "8": { 46 | "hi": [128, 128, 128], 47 | "normal": [128, 128, 128], 48 | "shadow_s_hi": [128, 128, 128], 49 | "shadow": [128, 128, 128], 50 | "2nd_shadow": [128, 128, 128] 51 | }, 52 | "9": { 53 | "hi": [128, 128, 128], 54 | "normal": [128, 128, 128], 55 | "shadow_s_hi": [128, 128, 128], 56 | "shadow": [128, 128, 128], 57 | "2nd_shadow": [128, 128, 128] 58 | }, 59 | "10": { 60 | "hi": [128, 128, 128], 61 | "normal": [128, 128, 128], 62 | "shadow": [128, 128, 128], 63 | "2nd_shadow": [128, 128, 128] 64 | }, 65 | "11": { 66 | "normal": [128, 128, 128] 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /sampleDatas/marked_colors.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/sampleDatas/marked_colors.JPG -------------------------------------------------------------------------------- /sampleDatas/sample_boxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/sampleDatas/sample_boxes.png -------------------------------------------------------------------------------- /sampleDatas/淮_sample_data/001_01_huai_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/sampleDatas/淮_sample_data/001_01_huai_normal.png -------------------------------------------------------------------------------- /sampleDatas/淮_sample_data/001_01_huai_normal.png_marked_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/sampleDatas/淮_sample_data/001_01_huai_normal.png_marked_colors.png -------------------------------------------------------------------------------- /sampleDatas/淮_sample_data/001_01_huai_normal_same_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/sampleDatas/淮_sample_data/001_01_huai_normal_same_color.png -------------------------------------------------------------------------------- /sampleDatas/淮_sample_data/color_chart_001_01_huai_normal.png.json: -------------------------------------------------------------------------------- 1 | { 2 | "001_01_huai_normal.png": { 3 | "1": { 4 | "hi": [ 5 | 81, 6 | 85, 7 | 93 8 | ], 9 | "normal": [ 10 | 65, 11 | 68, 12 | 70 13 | ], 14 | "shadow": [ 15 | 46, 16 | 46, 17 | 51 18 | ], 19 | "2nd_shadow": [ 20 | 33, 21 | 33, 22 | 37 23 | ] 24 | }, 25 | "2": { 26 | "hi": [ 27 | 93, 28 | 125, 29 | 126 30 | ], 31 | "normal": [ 32 | 89, 33 | 109, 34 | 111 35 | ], 36 | "shadow": [ 37 | 68, 38 | 70, 39 | 88 40 | ], 41 | "2nd_shadow": [ 42 | 51, 43 | 52, 44 | 66 45 | ] 46 | }, 47 | "3": { 48 | "hi": [ 49 | 90, 50 | 104, 51 | 99 52 | ], 53 | "normal": [ 54 | 55, 55 | 55, 56 | 56 57 | ], 58 | "shadow": [ 59 | 41, 60 | 41, 61 | 45 62 | ], 63 | "2nd_shadow": [ 64 | 19, 65 | 19, 66 | 21 67 | ] 68 | }, 69 | "4": { 70 | "hi": [ 71 | 245, 72 | 218, 73 | 208 74 | ], 75 | "normal": [ 76 | 240, 77 | 183, 78 | 158 79 | ], 80 | "shadow": [ 81 | 163, 82 | 115, 83 | 111 84 | ], 85 | "2nd_shadow": [ 86 | 143, 87 | 96, 88 | 90 89 | ] 90 | }, 91 | "5": { 92 | "hi": [ 93 | 111, 94 | 189, 95 | 138 96 | ], 97 | "normal": [ 98 | 112, 99 | 141, 100 | 119 101 | ], 102 | "shadow": [ 103 | 89, 104 | 99, 105 | 95 106 | ], 107 | "2nd_shadow": [ 108 | 72, 109 | 81, 110 | 77 111 | ] 112 | }, 113 | "6": { 114 | "hi": [ 115 | 66, 116 | 77, 117 | 84 118 | ], 119 | "normal": [ 120 | 58, 121 | 67, 122 | 73 123 | ], 124 | "shadow": [ 125 | 35, 126 | 38, 127 | 45 128 | ], 129 | "2nd_shadow": [ 130 | 26, 131 | 29, 132 | 34 133 | ] 134 | }, 135 | "7": { 136 | "hi": [ 137 | 81, 138 | 94, 139 | 97 140 | ], 141 | "normal": [ 142 | 74, 143 | 82, 144 | 84 145 | ], 146 | "shadow": [ 147 | 49, 148 | 48, 149 | 54 150 | ], 151 | "2nd_shadow": [ 152 | 36, 153 | 35, 154 | 39 155 | ] 156 | }, 157 | "8": { 158 | "hi": [ 159 | 255, 160 | 219, 161 | 204 162 | ], 163 | "normal": [ 164 | 253, 165 | 197, 166 | 167 167 | ], 168 | "shadow": [ 169 | 174, 170 | 120, 171 | 114 172 | ], 173 | "2nd_shadow": [ 174 | 151, 175 | 104, 176 | 98 177 | ] 178 | }, 179 | "9": { 180 | "hi": [ 181 | 253, 182 | 246, 183 | 244 184 | ], 185 | "normal": [ 186 | 253, 187 | 229, 188 | 211 189 | ], 190 | "shadow": [ 191 | 161, 192 | 128, 193 | 125 194 | ], 195 | "2nd_shadow": [ 196 | 133, 197 | 101, 198 | 102 199 | ] 200 | }, 201 | "10": { 202 | "hi": [ 203 | 189, 204 | 55, 205 | 76 206 | ], 207 | "normal": [ 208 | 150, 209 | 51, 210 | 67 211 | ], 212 | "shadow": [ 213 | 97, 214 | 38, 215 | 50 216 | ], 217 | "2nd_shadow": [ 218 | 73, 219 | 27, 220 | 37 221 | ] 222 | }, 223 | "11": { 224 | "hi": [ 225 | 74, 226 | 105, 227 | 124 228 | ], 229 | "normal": [ 230 | 51, 231 | 66, 232 | 75 233 | ], 234 | "shadow": [ 235 | 29, 236 | 36, 237 | 46 238 | ], 239 | "2nd_shadow": [ 240 | 18, 241 | 23, 242 | 30 243 | ] 244 | }, 245 | "12": { 246 | "normal": [ 247 | 113, 248 | 73, 249 | 97 250 | ] 251 | }, 252 | "13": { 253 | "hi": [ 254 | 95, 255 | 110, 256 | 104 257 | ], 258 | "normal": [ 259 | 40, 260 | 42, 261 | 48 262 | ], 263 | "shadow": [ 264 | 23, 265 | 24, 266 | 28 267 | ], 268 | "2nd_shadow": [ 269 | 15, 270 | 15, 271 | 17 272 | ] 273 | }, 274 | "14": { 275 | "normal": [ 276 | 73, 277 | 223, 278 | 152 279 | ] 280 | }, 281 | "15": { 282 | "hi": [ 283 | 84, 284 | 109, 285 | 97 286 | ], 287 | "normal": [ 288 | 89, 289 | 99, 290 | 95 291 | ], 292 | "shadow": [ 293 | 57, 294 | 63, 295 | 60 296 | ], 297 | "2nd_shadow": [ 298 | 43, 299 | 48, 300 | 46 301 | ] 302 | }, 303 | "16": { 304 | "normal": [ 305 | 49, 306 | 51, 307 | 58 308 | ], 309 | "shadow": [ 310 | 35, 311 | 38, 312 | 46 313 | ] 314 | }, 315 | "17": { 316 | "hi": [ 317 | 244, 318 | 245, 319 | 242 320 | ], 321 | "normal": [ 322 | 255, 323 | 163, 324 | 163 325 | ], 326 | "shadow": [ 327 | 250, 328 | 92, 329 | 95 330 | ], 331 | "2nd_shadow": [ 332 | 133, 333 | 46, 334 | 71 335 | ] 336 | }, 337 | "18": { 338 | "normal": [ 339 | 20, 340 | 20, 341 | 20 342 | ] 343 | }, 344 | "19": { 345 | "normal": [ 346 | 248, 347 | 230, 348 | 230 349 | ], 350 | "shadow": [ 351 | 212, 352 | 192, 353 | 184 354 | ] 355 | }, 356 | "20": { 357 | "normal": [ 358 | 243, 359 | 243, 360 | 243 361 | ], 362 | "shadow": [ 363 | 222, 364 | 221, 365 | 225 366 | ], 367 | "2nd_shadow": [ 368 | 196, 369 | 196, 370 | 196 371 | ] 372 | }, 373 | "21": { 374 | "normal": [ 375 | 196, 376 | 140, 377 | 132 378 | ], 379 | "shadow": [ 380 | 144, 381 | 95, 382 | 88 383 | ], 384 | "2nd_shadow": [ 385 | 126, 386 | 74, 387 | 73 388 | ] 389 | }, 390 | "22": { 391 | "normal": [ 392 | 68, 393 | 53, 394 | 53 395 | ], 396 | "shadow": [ 397 | 54, 398 | 41, 399 | 41 400 | ], 401 | "2nd_shadow": [ 402 | 41, 403 | 32, 404 | 32 405 | ] 406 | }, 407 | "23": { 408 | "normal": [ 409 | 255, 410 | 255, 411 | 0 412 | ] 413 | } 414 | } 415 | } -------------------------------------------------------------------------------- /sampleDatas/淮_sample_data/color_chart_001_01_huai_normal.png.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 644 7 | 8 | 9 | 10 | 2196 11 | 12 | 13 | 14 | 2196 15 | 16 | 17 | 18 | 2196 19 | 20 | 21 | 22 | 23 | 24 | 644 25 | 26 | 27 | 28 | 2196 29 | 30 | 31 | 32 | 2196 33 | 34 | 35 | 36 | 2196 37 | 38 | 39 | 40 | 41 | 42 | 644 43 | 44 | 45 | 46 | 2196 47 | 48 | 49 | 50 | 2196 51 | 52 | 53 | 54 | 2196 55 | 56 | 57 | 58 | 59 | 60 | 644 61 | 62 | 63 | 64 | 2196 65 | 66 | 67 | 68 | 2196 69 | 70 | 71 | 72 | 2196 73 | 74 | 75 | 76 | 77 | 78 | 644 79 | 80 | 81 | 82 | 2196 83 | 84 | 85 | 86 | 2196 87 | 88 | 89 | 90 | 2196 91 | 92 | 93 | 94 | 95 | 96 | 644 97 | 98 | 99 | 100 | 2196 101 | 102 | 103 | 104 | 2196 105 | 106 | 107 | 108 | 2196 109 | 110 | 111 | 112 | 113 | 114 | 644 115 | 116 | 117 | 118 | 2196 119 | 120 | 121 | 122 | 2196 123 | 124 | 125 | 126 | 2196 127 | 128 | 129 | 130 | 131 | 132 | 644 133 | 134 | 135 | 136 | 2196 137 | 138 | 139 | 140 | 2196 141 | 142 | 143 | 144 | 2196 145 | 146 | 147 | 148 | 149 | 150 | 644 151 | 152 | 153 | 154 | 2196 155 | 156 | 157 | 158 | 2196 159 | 160 | 161 | 162 | 2196 163 | 164 | 165 | 166 | 167 | 168 | 644 169 | 170 | 171 | 172 | 2196 173 | 174 | 175 | 176 | 2196 177 | 178 | 179 | 180 | 2196 181 | 182 | 183 | 184 | 185 | 186 | 644 187 | 188 | 189 | 190 | 2196 191 | 192 | 193 | 194 | 2196 195 | 196 | 197 | 198 | 2196 199 | 200 | 201 | 202 | 203 | 204 | 2769 205 | 206 | 207 | 208 | 209 | 210 | 644 211 | 212 | 213 | 214 | 2196 215 | 216 | 217 | 218 | 2196 219 | 220 | 221 | 222 | 2196 223 | 224 | 225 | 226 | 227 | 228 | 2769 229 | 230 | 231 | 232 | 233 | 234 | 644 235 | 236 | 237 | 238 | 2196 239 | 240 | 241 | 242 | 2196 243 | 244 | 245 | 246 | 2196 247 | 248 | 249 | 250 | 251 | 252 | 2196 253 | 254 | 255 | 256 | 2196 257 | 258 | 259 | 260 | 261 | 262 | 644 263 | 264 | 265 | 266 | 2196 267 | 268 | 269 | 270 | 2196 271 | 272 | 273 | 274 | 2196 275 | 276 | 277 | 278 | 279 | 280 | 2769 281 | 282 | 283 | 284 | 285 | 286 | 2196 287 | 288 | 289 | 290 | 2196 291 | 292 | 293 | 294 | 295 | 296 | 2562 297 | 298 | 299 | 300 | 2562 301 | 302 | 303 | 304 | 2562 305 | 306 | 307 | 308 | 309 | 310 | 2562 311 | 312 | 313 | 314 | 2562 315 | 316 | 317 | 318 | 2562 319 | 320 | 321 | 322 | 323 | 324 | 2562 325 | 326 | 327 | 328 | 2562 329 | 330 | 331 | 332 | 2562 333 | 334 | 335 | 336 | 337 | 338 | 2769 339 | 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /screenshoots/2.4main.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/2.4main.PNG -------------------------------------------------------------------------------- /screenshoots/aescriptmain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/aescriptmain.png -------------------------------------------------------------------------------- /screenshoots/aescripttypepanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/aescripttypepanel.png -------------------------------------------------------------------------------- /screenshoots/colorpicker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/colorpicker.gif -------------------------------------------------------------------------------- /screenshoots/configjson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/configjson.png -------------------------------------------------------------------------------- /screenshoots/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/debug.png -------------------------------------------------------------------------------- /screenshoots/func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/func.png -------------------------------------------------------------------------------- /screenshoots/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/json.png -------------------------------------------------------------------------------- /screenshoots/mian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/mian.png -------------------------------------------------------------------------------- /screenshoots/mian_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/mian_cn.png -------------------------------------------------------------------------------- /screenshoots/usrboxmgn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/usrboxmgn.png -------------------------------------------------------------------------------- /screenshoots/usrboxreg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/usrboxreg.png -------------------------------------------------------------------------------- /screenshoots/xml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/screenshoots/xml.png -------------------------------------------------------------------------------- /supportedBoxes/00_04.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/supportedBoxes/00_04.PNG -------------------------------------------------------------------------------- /supportedBoxes/00_05.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/supportedBoxes/00_05.PNG -------------------------------------------------------------------------------- /supportedBoxes/00_07.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/supportedBoxes/00_07.PNG -------------------------------------------------------------------------------- /supportedBoxes/01_04.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/supportedBoxes/01_04.PNG -------------------------------------------------------------------------------- /supportedBoxes/02_00.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/supportedBoxes/02_00.PNG -------------------------------------------------------------------------------- /supportedBoxes/03_00.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChenxingM/AutoColorChart/24a71a955ec32dc798d892506efc767f9060af9d/supportedBoxes/03_00.PNG --------------------------------------------------------------------------------