您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自動でキャンバスに画像を描画します。
// ==UserScript== // @name ピクトセンス - 自動描画[EXT] // @namespace http://tampermonkey.net/ // @version 1.0.0 // @license MIT // @description 自動でキャンバスに画像を描画します。 // @author You // @match https://pictsense.com/* // @require http://code.jquery.com/jquery-3.5.1.min.js // @require https://greasyfork.org/scripts/419945-global-managedextensions/code/Global_ManagedExtensions.js?version=889360 // @require https://greasyfork.org/scripts/419888-antimatterx/code/antimatterx.js?version=889299 // @grant GM.setValue // @grant GM.getValue // ==/UserScript== (function(unsafeWindow) { 'use strict'; const $ = window.$, amx = window.antimatterx; let input_colors, input_resolution, monochrome_flag, input_time, loop_max, paintLast_flag, Message, input_file, g_stop_btn_holder, run_flag, g_stop_flag; function setConfig() { const h = $("<div>"), message_holder = $("<div>").appendTo(h); Message = function(s) { message_holder.text(s); }; g_stop_btn_holder = $("<div>").appendTo(h); input_colors = $(amx.addInputRange(h[0], { title: "クオリティ", width: "40%", min: 2, max: 256, save: "input_colors" })); input_resolution = $(amx.addInputRange(h[0], { title: "解像度", width: "40%", min: 10, max: 550, save: "input_resolution" })); monochrome_flag = $(amx.addInputBool(h[0], { title: "モノクロ" })); reset_input_file(); amx.addButton(h[0], { title: "画像を読み込む", click: function() { if (run_flag) Message("自動描画中なので読み込めません。"); else input_file.click(); } }); paintLast_flag = $(amx.addInputBool(h[0], { title: "後ろから塗る" })); input_time = $(amx.addInputNumber(h[0], { title: "描画間隔", placeholder: "[秒]", value: 1, save: "input_time", max: 5, min: 0 })).val(1); loop_max = $(amx.addInputNumber(h[0], { title: "ループ最大数", width: "5em", value: 3777, save: "loop_max", max: 3777, min: 0 })).val(3777); h.children().each(function(i, e) { $(e).after("<br>"); }); return h; }; unsafeWindow.Global_ManagedExtensions["自動描画"] = { config: setConfig, tag: "ピクトセンス" }; const CV_ELM = $("#previewCanvas"); // canvas要素 let g_W, g_H, // 画像の幅と高さ g_pixelClass_Array, // クラスの入れ物 g_ADD_X, g_ADD_Y, g_cv_click_flag = false; function reset() { run_flag = false; reset_input_file(); g_cv_click_flag = false; g_stop_btn_holder.empty(); }; function mouse_event(evt_name, x, y, jq) { const evt = document.createEvent("MouseEvent"); evt.initMouseEvent( evt_name, // type 設定可能なタイプは click, mosuedown, mouseup, mouseover, mousemove, mouseout true, // canBubble bubbleを許可するかどうか true, // cancelable 途中で処理を止められるかどうか unsafeWindow, // view 処理させるウィンドウのオブジェクト 1, // detail マウスクリックの回数 0, // screenX 0, // screenY x - $(window).scrollLeft(), // clientX y - $(window).scrollTop(), // clientY false, // ctrlKey イベント中にCtrlキーを押した状態にするかどうか false, // altKey イベント中にAltキーを押した状態にするかどうか false, // shiftKey イベント中にShiftキーを押した状態にするかどうか false, // metaKey イベント中にMetaキーを押した状態にするかどうか 0, // button 0を設定すると左クリック、1で中クリック、2で右クリック unsafeWindow // relatedTarget 関連するイベントの設定。MouseOverとMouseOutの時だけ使用するのでそれ以外はnull ); jq.get(0).dispatchEvent(evt); }; function setRGBA(_RGBA) { let RGB = ""; for (let i = 0; i < 3; i++) RGB += ("00" + Number(_RGBA[i]).toString(16)).slice(-2); const btn_elm = $("#colorPalette").find("button").eq(0); btn_elm.attr("data-color", RGB); btn_elm[0].style.backgroundColor = "#" + RGB; mouse_event("mousedown", btn_elm.offset().left, btn_elm.offset().top, btn_elm); const A = _RGBA[3], sld_elm = $("#opacitySlider"), Convers = (A / 256) * sld_elm.width(); mouse_event("mousedown", sld_elm.offset().left + Convers, sld_elm.offset().top, sld_elm); mouse_event("mouseup", 0, 0, sld_elm); const size_elm = $("#sizeButtonHolder").find("button").eq(0); mouse_event("mousedown", size_elm.offset().left, size_elm.offset().top, size_elm); mouse_event("mouseup", 0, 0, size_elm); }; class pixelClass { constructor(_x, _y, _RGBA) { const ar = []; const divide = 256 / input_colors.val(); for (let i = 0; i < 4; i++) ar.push(Math.floor(Math.ceil(_RGBA[i] / divide) * divide)); this._RGBA = ar.join("_"); this._flag = ar[0] === 255 && ar[1] === 255 && ar[2] === 255 || ar[3] === 0 ? true : false; this.m_y = { "isFirst": _y == 0 ? true : false, "isEnd": _y == g_H - 1 ? true : false }; this.m_x = { "isFirst": _x == 0 ? true : false, "isEnd": _x == g_W - 1 ? true : false }; }; get getRGBA() { return this._RGBA; }; get _getFlag() { return this._flag; }; setFlag() { this._flag = true; }; judge(_RGBA) { if (this._getFlag) return false; if (this.getRGBA != _RGBA) return false; return true; }; static make(_ImageData) { const H = _ImageData.height; const W = _ImageData.width; const array = []; for (let y = 0; y < H; y++) { array.push([]); for (let x = 0; x < W; x++) { const index = (x + y * W) * 4; const R = _ImageData.data[index]; const G = _ImageData.data[index + 1]; const B = _ImageData.data[index + 2]; const A = _ImageData.data[index + 3]; array[y].push(new pixelClass(x, y, [R, G, B, A])); }; }; return array; }; static search_false(_pixelObjArray) { if (paintLast_flag.find("input[type='checkbox']").prop("checked")) { for (let y = g_H - 1; y >= 0; y--) { for (let x = g_W - 1; x >= 0; x--) { if (_pixelObjArray[y][x]._getFlag == false) return [x, y]; }; }; } else { for (let y = 0; y < g_H; y++) { for (let x = 0; x < g_W; x++) { if (_pixelObjArray[y][x]._getFlag == false) return [x, y]; }; }; }; return null; }; }; function resize(w1, h1) { const W_MAX = CV_ELM.width(), H_MAX = CV_ELM.height(); if (w1 <= W_MAX && h1 <= H_MAX) return [w1, h1]; let w2, h2; if (w1 > h1) { const ratio = h1 / w1; w2 = W_MAX; h2 = Math.floor(W_MAX * ratio); } else { const ratio = w1 / h1; h2 = H_MAX; w2 = Math.floor(H_MAX * ratio); }; console.log(w2 + " " + h2); return [w2, h2]; }; const reader = new FileReader(); reader.addEventListener("load", function() { const img = new Image(); img.src = reader.result; img.addEventListener("load", function() { Message("読み込み完了"); const result = resize(img.width, img.height); g_W = result[0]; g_H = result[1]; const cv = document.createElement("canvas"); cv.width = g_W; cv.height = g_H; const ct = cv.getContext("2d"); ct.drawImage(img, 0, 0, img.width, img.height, 0, 0, g_W, g_H); g_pixelClass_Array = pixelClass.make(ct.getImageData(0, 0, cv.width, cv.height)); alert('画像が読み込み終わりました\n出力する位置をクリックしてください'); Message('キャンバスのどこかをクリック'); g_cv_click_flag = true; }); }); const Auto_Draw = (_Start_x, _Start_y) => { const move_log = []; move_log.push([_Start_x, _Start_y]); const nowRGBA = g_pixelClass_Array[_Start_y][_Start_x].getRGBA; setRGBA(nowRGBA.split("_")); mouse_event("mousedown", _Start_x + g_ADD_X, _Start_y + g_ADD_Y, CV_ELM); let n_x = _Start_x, n_y = _Start_y, way_log = null, break_counter = 0; while (1) { break_counter++; if (loop_max.val() < break_counter) { mouse_event("mouseup", 0, 0, CV_ELM); // 中断 break; }; if (g_stop_flag) return mouse_event("mouseup", 0, 0, CV_ELM); const now = g_pixelClass_Array[n_y][n_x]; now.setFlag(); const ar = []; if (!now.m_y.isFirst) ar.push("up", n_x, n_y - 1); if (!now.m_x.isFirst) ar.push("left", n_x - 1, n_y); if (!now.m_y.isFirst && !now.m_x.isFirst) ar.push("up_left", n_x - 1, n_y - 1); if (!now.m_y.isEnd) ar.push("under", n_x, n_y + 1); if (!now.m_x.isEnd) ar.push("right", n_x + 1, n_y); if (!now.m_y.isEnd && !now.m_x.isEnd) ar.push("under_right", n_x + 1, n_y + 1); if (!now.m_y.isFirst && !now.m_x.isEnd) ar.push("up_right", n_x + 1, n_y - 1); if (!now.m_y.isEnd && !now.m_x.isFirst) ar.push("under_left", n_x - 1, n_y + 1); const ar2 = []; for (let i = 0; i < (ar.length) / 3; i++) { if (g_pixelClass_Array[ar[3 * i + 2]][ar[3 * i + 1]].judge(nowRGBA)) ar2.push(ar[3 * i], ar[3 * i + 1], ar[3 * i + 2]); } if (!ar2.length) { if (!move_log.length) { mouse_event("mouseup", 0, 0, CV_ELM); // 探索終了 break; } const move_log_end = move_log.pop(); n_x = move_log_end[0]; n_y = move_log_end[1]; way_log = null; mouse_event("mousemove", n_x + g_ADD_X, n_y + g_ADD_Y, CV_ELM); continue; } let next = ar2.indexOf(way_log); if (next === -1) next = 0; if (1 < ar2.length) move_log.push([n_x, n_y]); way_log = ar2[next]; n_x = ar2[next + 1]; n_y = ar2[next + 2]; mouse_event("mousemove", n_x + g_ADD_X, n_y + g_ADD_Y, CV_ELM); }; const result = pixelClass.search_false(g_pixelClass_Array); if (result) { n_x = result[0]; n_y = result[1]; const SIZE = g_W * g_H; let now_position = g_W * n_y + n_x; if (paintLast_flag.find("input[type='checkbox']").prop("checked")) now_position = SIZE - now_position; Message("自動描画中…(" + Math.floor(((now_position / SIZE) * 100) * 1000) / 1000 + "%)"); setTimeout(function() { Auto_Draw(n_x, n_y); }, 1000 * input_time.val()); } else { Message("★描画完了(" + new Date().toString().match(/[0-9]{2}:[0-9]{2}:[0-9]{2}/)[0] + ")"); reset(); return; }; }; function reset_input_file() { input_file = $("<input>", { type: "file" }).change(function(e) { if (!e.target.files[0]) return; Message("画像を読み込み中"); reader.readAsDataURL(e.target.files[0]); }); }; function add_stop_button() { $(amx.addButton(g_stop_btn_holder[0], { title: "緊急停止", click: function() { g_stop_flag = true; Message("緊急停止しました"); reset(); } })).css("color", "red"); }; CV_ELM[0].addEventListener("click", function(e) { if (g_cv_click_flag === false) return; g_ADD_X = e.clientX; g_ADD_Y = e.clientY; alert("自動描画を始めます"); run_flag = true; add_stop_button(); g_stop_flag = false; if (paintLast_flag.find("input[type='checkbox']").prop("checked")) Auto_Draw(g_W - 1, g_H - 1); else Auto_Draw(0, 0); g_cv_click_flag = false; }, false); })(this.unsafeWindow || window);