其他分享
首页 > 其他分享> > 画画

画画

作者:互联网

(() => {
function log(...args) {
let dateNow = new Date(Date.now());
function fix2(number) {
number = Math.floor(number);
return number >= 1 ? number >= 10 ? number.toFixed(0) : `0${number}` : '00';
}
let a = `${fix2(dateNow.getHours())}:${fix2(dateNow.getMinutes())}`;
let key = `${dateNow.toDateString()} ${a}`;
let middle = `${fix2(dateNow.getSeconds())}.${fix2(dateNow.getMilliseconds() / 10)}`;
let value = args.map(String).join(' ');
if (typeof window.localStorage === 'object') {
localStorage.setItem(key, `${localStorage.getItem(key) ? `${localStorage.getItem(key)}\n` : ''}[${middle}] ${value}`);
}
console.log(`${a}:${middle} ${value}`);
}
function parseCanvas(canvas, doTranspose = false) {
let height = canvas.height;
let width = canvas.width;
let data = canvas.getContext('2d').getImageData(0, 0, width, height).data;
let result = [];
for (let x = 0; x < height; x++) {
for (let y = 0; y < width; y++) {
let index = (x * width + y) * 4;
result.push({
x: doTranspose ? y : x,
y: doTranspose ? x : y,
color: data.slice(index, index + 4)
});
}
}
return result;
}

function parseColorString(colorString) {
let canvas = document.createElement('canvas');
canvas.height = 1;
canvas.width = 1;
let cxt = canvas.getContext('2d');
const MAGIC_COLOR_1 = '#123456';
cxt.fillStyle = MAGIC_COLOR_1;
cxt.fillStyle = colorString;
if (cxt.fillStyle === MAGIC_COLOR_1) {
const MAGIC_COLOR_2 = '#234567';
cxt.fillStyle = MAGIC_COLOR_2;
cxt.fillStyle = colorString;
if (cxt.fillStyle === MAGIC_COLOR_2) {
throw new Error('invaild color');
}
}
cxt.fillRect(0, 0, 1, 1);
return parseCanvas(canvas)[0].color;
}

const H = window.H || 400;
const W = window.W || 800;
const COLORLIST = (window.colorlist || ["rgb(0, 0, 0)", "rgb(255, 255, 255)", "rgb(170, 170, 170)", "rgb(85, 85, 85)", "rgb(254, 211, 199)", "rgb(255, 196, 206)", "rgb(250, 172, 142)", "rgb(255, 139, 131)", "rgb(244, 67, 54)", "rgb(233, 30, 99)", "rgb(226, 102, 158)", "rgb(156, 39, 176)", "rgb(103, 58, 183)", "rgb(63, 81, 181)", "rgb(0, 70, 112)", "rgb(5, 113, 151)", "rgb(33, 150, 243)", "rgb(0, 188, 212)", "rgb(59, 229, 219)", "rgb(151, 253, 220)", "rgb(22, 115, 0)", "rgb(55, 169, 60)", "rgb(137, 230, 66)", "rgb(215, 255, 7)", "rgb(255, 246, 209)", "rgb(248, 203, 140)", "rgb(255, 235, 59)", "rgb(255, 193, 7)", "rgb(255, 152, 0)", "rgb(255, 87, 34)", "rgb(184, 63, 39)", "rgb(121, 85, 72)"]).map(parseColorString);
const COOLDOWN = window.timelimit || 10;
const fetcher = {
async paint({ x, y, colorID }) {
let result = await (window.jQuery
? jQuery.post('/paintBoard/paint', {
x: y,
y: x,
color: colorID,
}).promise()
: fetch('/paintBoard/paint', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `x=${y}&y=${x}&color=${colorID}`,
}).then(result => result.json()));
if (result.status !== 200) {
throw new Error(result.data);
}
},
async getBoard() {
let data = await (window.jQuery
? jQuery.get('/paintBoard/board').promise()
: fetch('/paintBoard/board', {
method: 'GET',
credentials: 'include',
}).then(result => result.text()));
let turnedResult = data.split('\n').slice(0, W).map(line => Array.from(line).map(colorAlphaID => parseInt(colorAlphaID, 36)).slice(0, H));
let result = new Uint8Array(H * W);
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
let index = x * W + y;
result[index] = turnedResult[y][x];
}
}
return result;
},
};
function colorDistance([r1, g1, b1], [r2, g2, b2]) {
const ar = (r1 + r2) >> 1;
const dr = r1 - r2;
const dg = g1 - g2;
const db = b1 - b2;
return (
(2 + (ar >> 8)) * dr ** 2
+ ((dg ** 2) << 2)
+ (3 - (ar >> 8)) * db ** 2
);
}

function nearestColorID(color) {
let resultID;
let resultDistance = Infinity;
COLORLIST.forEach((targetColor, colorID) => {
let distance = colorDistance(color, targetColor);
if (distance < resultDistance) {
resultID = colorID;
resultDistance = distance;
}
});
return resultID;
}

function loadImage(src) {
return new Promise((resolve, reject) => {
let image = document.createElement('img');
image.crossOrigin = 'anonymous';
image.src = `https://cors-anywhere.herokuapp.com/${src}`;
image.addEventListener('load', () => {
resolve(image);
});
image.addEventListener('error', e => {
reject(new Error(e.message));
});
});
}

function parseImage(imageElement) {
let canvas = document.createElement('canvas');
let height = imageElement.height;
let width = imageElement.width;
canvas.height = height;
canvas.width = width;
let cxt = canvas.getContext('2d');
cxt.drawImage(imageElement, 0, 0);
return parseCanvas(canvas);
}

function formatTask(list) {
return list.filter(({ x, y, color: [, , , a] }) => x >= 0 && x <= H - 1 && y >= 0 && y <= W - 1 && a !== 0).map(({ x, y, color }) => ({
x,
y,
colorID: nearestColorID(color),
}));
}
function generateCanvas(list) {
// console.log(list);
let height = 0;
let width = 0;
for (let { x, y } of list) {
height = Math.max(height, x + 1);
width = Math.max(width, y + 1);
}
let canvas = document.createElement('canvas');
canvas.height = height;
canvas.width = width;
let cxt = canvas.getContext('2d');
let imageData = cxt.createImageData(width, height);
for (let { x, y, color } of list) {
let index = (x * width + y) * 4;
for (let i = 0; i < 4; i++) {
imageData.data[index + i] = color[i];
}
}
cxt.putImageData(imageData, 0, 0);
return canvas;
}
function preview(task) {
// console.log(task);
let canvas = generateCanvas(task.map(({ x, y, colorID }) => ({ x, y, color: COLORLIST[colorID] })));
canvas.style.width = 'fit-content';
canvas.title = 'Press G to toggle';
let element=document.getElementById('mycanvas');
let parent;
if(element!==null){
canvas.style.position='absolute';
canvas.style.left='10px';
canvas.style.top='0';
parent=element.parentElement;
}
else{
parent=document.body;
}
let viewing=false;
window.addEventListener('keydown',({key})=>{
if(key.toLowerCase()==='g'){
viewing=!viewing;
if(viewing){
parent.appendChild(canvas);
}
else{
parent.removeChild(canvas);
}
}
})
}

const parser = {
altTable: {
x: ['i', 0],
y: ['j', 1],
color: ['color', 'c', 'id', 'colorID', 2],
r: ['red'],
g: ['green'],
b: ['blue'],
a: ['alpha'],
source: ['original', 'src', 'href', 's'],
scaleX: ['sx'],
scaleY: ['sy'],
deltaX: ['dx'],
deltaY: ['dy'],
height: ['h', 'dh', 'targetHeight'],
width: ['w', 'dw', 'targetWidth'],
},
access(obj, name) {
if (typeof obj !== 'object') {
throw new Error('not an object');
}
if (name in obj) {
return obj[name];
}
else if (name in this.altTable) {
let table = this.altTable[name];
for (let key of table) {
if (key in obj) {
return obj[key];
}
}
}
throw new Error(`prop ${name} not found`);
},
accesses(obj, names) {
return names.map(name => this.access(obj, name));
},
async mergeError(map) {
let messages = [];
for (let name in map) {
let content = map[name];
try {
return await content();
}
catch (error) {
messages.push(`${name}:${error.message}`);
}
}
throw new Error(`{${messages.join(',')}}`);
},
checkInteger(number, min = -Infinity, max = Infinity) {
if (!Number.isInteger(number)) {
throw new Error('not an integer');
};
if (number < min || number > max) {
throw new Error(`not in range [${min},${max}]`);
}
},
unRangedColor(colorLike) {
return this.mergeError({
colorArray() {
let [r, g, b, a = 255] = colorLike;
return [r, g, b, a];
},
colorObject: () => {
let [r, g, b] = this.accesses(colorLike, ['r', 'g', 'b']);
let a;
try {
a = this.access(colorLike, 'a');
}
catch (_) {
a = 255;
}
return [r, g, b, a];
},
colorString() {
return parseColorString(colorLike);
},
});
},
color(colorLike) {
// console.log('colorID',colorIDLike);
return this.mergeError({
colorID: () => {
this.checkInteger(colorLike, 0, COLORLIST.length - 1);
return COLORLIST[colorLike];
},
rgb: async () => {
let color = await this.unRangedColor(rgbLike);
color.forEach(x => {
this.checkInteger(x, 0, 255);
});
return color;
},
});
},
async point(pointLike) {
// console.log('point',pointLike);
let [x, y, colorLike] = this.accesses(pointLike, ['x', 'y', 'color']);
this.checkInteger(x, 0, H - 1);
this.checkInteger(y, 0, W - 1);
return { x, y, color: await this.color(colorLike) };
},
async image(imageLike) {
if (typeof imageLike === 'string') {
const imageElement = await loadImage(imageLike);
return parseImage(imageElement);
}
else {
throw new Error('src is not a string');
}
},
normalizeTask(task) {
let points = new Map();
let result = [];
task.reverse();
task.forEach(point => {
const { x, y } = point;
let vaild = true;
if (points.has(x)) {
let mapX = points.get(x);
if (mapX.has(y)) {
vaild = false;
}
else {
mapX.add(y);
}
}
else {
points.set(x, new Set([y]));
}
if (vaild) {
result.push(point);
}
});
task.reverse();
return result;
},
checkNumber(number, min = -Infinity, max = Infinity) {
if (typeof number !== 'number' || Number.isNaN(number)) {
throw new Error('not a number');
}
if (number < min || number > max) {
throw new Error(`not in range [${min},${max}]`);
}
},
async transform(transformLike) {
let source = await this.task(this.access(transformLike, 'source'));
let canvas = generateCanvas(source);
let height = canvas.height;
let width = canvas.width;

let targetHeight = height;
let targetWidth = width;
let doTranspose = false;
let deltaX = 0;
let deltaY = 0;
try {
let temp;
try {
temp = Math.round(this.access(transformLike, 'scaleX') * height);
}
catch (_) {
temp = this.access(transformLike, 'height');
}
this.checkInteger(temp, 0);
targetHeight = temp;
} catch (_) { }
try {
let temp;
try {
temp = Math.round(this.access(transformLike, 'scaleX') * width);
}
catch (_) {
temp = this.access(transformLike, 'width');
}
this.checkInteger(temp, 0);
targetWidth = temp;
} catch (_) { }
try {
let temp;
temp = this.access(transformLike, 'deltaX');
this.checkInteger(temp);
deltaX = temp;
} catch (_) { }
try {
let temp;
temp = this.access(transformLike, 'deltaY');
this.checkInteger(temp);
deltaY = temp;
} catch (_) { }
try {
doTranspose = this.access(transformLike, 'doTranspose');
if (typeof doTranspose !== 'boolean') {
doTranspose = false;
throw new Error('not a boolean');
}
} catch (_) { }

if (doTranspose) {
([deltaX, deltaY] = [deltaY, deltaX]);
}
// console.log({deltaX,deltaY,doTranspose,targetHeight,targetWidth});
let targetCanvas = document.createElement('canvas');
targetCanvas.height = targetHeight;
targetCanvas.width = targetWidth;
let cxt = targetCanvas.getContext('2d');
cxt.drawImage(canvas, 0, 0, targetWidth, targetHeight);
return parseCanvas(targetCanvas, doTranspose).map(({ x, y, color }) => ({ x: x + deltaX, y: y + deltaY, color }));
},
async blog(blogLike) {
let link = this.access(blogLike, 'blog');
let result = await fetch(link);
let html = await result.text();
let parts = html.match(/<code.*?>((?:.|\n)*?)<\/code>/mg);
return this.mergeError(parts.map(part => () => {
// console.log(part);
let div = document.createElement('div');
div.innerHTML = part.match(/<code.*?>((?:.|\n)*?)<\/code>/m)[1];
let json = div.innerText;
return this.task(JSON.parse(json));
}));
},
async task(taskLike) {
// console.log('task',taskLike);
function flat(array) {
let result = [];
for (let subArray of array) {
for (let item of subArray) {
result.push(item);
}
}
return result;
}
let task = await this.mergeError({
point: async () => [await this.point(taskLike)],
transform: async () => await this.transform(taskLike),
blog: async () => await this.blog(taskLike),
image: () => this.image(taskLike),
taskArray: async () => flat(await Promise.all(taskLike.map(task => this.task(task)))),
});
return this.normalizeTask(task);
},
};

function wait(s) {
return new Promise(resolve => {
setTimeout(resolve, s * 1000);
});
}

const manager = {
callbacks: {
update: [],
result: [],
inited: [],
},
cookies: new Map(),
on(event, callback) {
this.callbacks[event].push(callback);
},
emit(event, ...args) {
this.callbacks[event].forEach(callback => {
callback(...args);
});
},
async init() {
const [, board] = await Promise.all([new Promise((resolve, reject) => {
this.ws = new WebSocket('wss://ws.luogu.com.cn/ws');
this.ws.addEventListener('open', () => {
this.ws.send(JSON.stringify({
type: 'join_channel',
channel: 'paintboard',
channel_param: ''
}));
this.ws.addEventListener('message', ({ data }) => {
const { type, ...args } = JSON.parse(data);
switch (type) {
case 'paintboard_update': {
this.emit('update', {
x: args.y,
y: args.x,
colorID: args.color,
});
break;
}
case 'result': {
this.emit('result');
break;
}
}
});
resolve();
});
this.ws.addEventListener('error', error => {
reject(error.message);
});
}), fetcher.getBoard()]);
this.board = board;
this.tasks = [];
this.target = new Int8Array(H * W).fill(-1);
this.differs = [];
this.clearTask();
if(typeof window.update==='function'){
let x=()=>{};
window.addEventListener('keydown',({key})=>{
if(key.toLowerCase()==='f'){
([window.update,x]=[x,window.update]);
}
});
}
this.on('update', ({ x, y, colorID: newColorID }) => {
// log('更新',x,y,newColorID);
let index = x * W + y;
let targetColorID = this.target[index];
if (targetColorID !== -1) {
let oldColorID = this.board[index];
if (oldColorID === targetColorID
&& newColorID !== targetColorID) {
this.differs.push({ x, y });
}
}
this.board[index] = newColorID;
});
this.emit('inited');
let tick = 0;
let shift = 0;
while (true) {
let cooldown = Math.max(0.1, COOLDOWN / Math.max(this.cookies.size, 1));
// console.log('differL', this.differs.length);
if (this.differs.length > 0) {
let id = Math.floor(Math.random() * this.differs.length);
let { x, y } = this.differs[id];
// console.log(x, y);
let last = this.differs.pop();
if (id < this.differs.length) {
this.differs[id] = last;
}
let index = x * W + y;
let targetColorID = this.target[index];
if (this.board[index] !== targetColorID) {
// console.log('diff');
let success = false;
if (this.cookies.size > 0) {
let [_uid, __client_id] = [...this.cookies][shift % this.cookies.size];
document.cookie = `_uid=${_uid};__client_id=${__client_id}`;
}
log('绘制', x, y, targetColorID);
for (let t = 0.01; t < Math.max(0.02, cooldown / 3); t *= 1.5) {
try {
await Promise.all([fetcher.paint({ x, y, colorID: targetColorID }),wait(cooldown)]);
success = true;
shift++;
break;
}
catch (error) {
await wait(t);
}
}
if (success) {
tick++;
}
else {
log('绘制失败!');
this.differs.push({ x, y });
}
}
}
else {
tick++;
await wait(1);
}
if (tick % 100 === 1) {
await this.refresh();
}
}
},
async refresh() {
let board = await fetcher.getBoard();
for (let x = 0; x < H; x++) {
for (let y = 0; y < W; y++) {
let index = x * W + y;
if (this.board[index] !== board[index]) {
this.emit('update', { x, y, colorID: board[index] });
}
}
}
},
addTask(task) {
for (let { x, y, colorID } of task) {
let index = x * W + y;
let targetColorID = this.target[index];
if (targetColorID !== -1 && targetColorID !== colorID) {
throw new Error(`tasks conflict at (x=${x},y=${y})`);
}
}
for (let { x, y, colorID } of task) {
let index = x * W + y;
if (this.target[index] !== colorID) {
this.target[index] = colorID;
if (this.board[index] !== colorID) {
this.differs.push({ x, y });
}
}
}
},
clearTask() {
this.tasks = [];
this.target = new Int8Array(H * W).fill(-1);
this.differs = [];
},
addCookie(uid, clientID) {
this.cookies.set(uid, clientID);
},
removeCookie(uid) {
this.cookies.delete(uid);
}
};

return new Promise((resolve, reject) => {
manager.init();
manager.on('inited', () => {
function draw(task, x, y) {
draw.clearTask();
draw.addTask(task, x, y);
}
draw.taskFromLittleMing = { blog: 'https://www.luogu.com.cn/blog/user25512/paint' };
draw.draw = draw;
draw.addTask = async function addTask(task, x, y) {
task = await parser.task(task);
try {
parser.checkInteger(x, 0, H - 1);
parser.checkInteger(y, 0, W - 1);
task.forEach(point => {
point.x += x;
point.y += y;
});
} catch (e) {
// console.error(e);
}
task=formatTask(task);
preview(task);
manager.addTask(task);
}
draw.clearTask = function clearTask() {
manager.clearTask();
}
draw.addCookie = function addCookie(uid, clientID) {
manager.addCookie(uid, clientID);
}
draw.removeCookie = function removeCookie(uid) {
manager.removeCookie(uid);
}
window.draw = draw;
log('加载完成');
resolve();
});
});
})().then(() => {

// 把你的代码写在这个花括号里

/**
* x 坐标值表示行号(从上到下),范围 [0,399]
* y 坐标值表示列号(从左到右),范围 [0,799]
* 颜色编号范围 [0,31]
*/

/**
* 在 x=0, y=0(左上角)处绘制一个颜色编号为 2(灰色)的像素
* draw({x:0,y:0,colorID:2});
*/

/**
* 新的 draw 调用会覆盖原来的
* 在 x=0, y=799(右上角)处绘制一个颜色为 rgb(255,255,255) 的像素
* 在 x=399, y=0(左下角)处绘制一个颜色为 rgb(170,170,170) 的像素
* 在 x=399, y=799(右下角)处绘制一个颜色为 rgb(0,0,0) 的像素
* draw([
* {x:0,y:799,color:{r:255,g:255,b:255}},
* {x:399,y:0,color:[170,170,170]},
* {x:399,y:799,color:'#000000'}
* ]);
*/

/**
* 以 x=400, y=500 为左上角绘制 https://cdn.luogu.com.cn/upload/usericon/115864.png(神鱼的洛谷头像)
* draw('https://cdn.luogu.com.cn/upload/usericon/115864.png',400,500);
*/
draw('https://cdn.luogu.com.cn/upload/image_hosting/nxn8ao34.png',350,350);
/**
* 增加一些 Cookie
* 注意:使用增加 Cookie 功能需要你删除由洛谷创建的那个 Cookie!
* draw.addCookie('25512','...') // _uid, __client_id
* draw.addCookie('123456','...')
* draw.addCookie('25512','...') // uid 相同会覆盖 Cookie
* draw.removeCookie('123456') // 移除 Cookie
*/

/**
* 帮帮蒟蒻 Little_Ming 吧 QAQ
*/
// draw(draw.taskFromLittleMing);

/**
* 图片的放缩与平移
* draw({
* source:'https://cdn.luogu.com.cn/upload/usericon/115864.png', // %%% 神鱼!
* height:50, // 目标高度
* width:200, // 目标宽度
* deltaX:200, // 平移
* deltaY:200, // 总是先放缩再平移
* doTranspose:false, // 是否交换 XY?如果为 true,那么在平移之后交换所有点的 XY 坐标值。
* });
*/

/**
* 当然,你可以把各种东西一起画~
* draw([
* {
* source:[
* {x:200,y:555,color:8}
* {x:255,y:300,color:6},
* ],
* deltaX:100,
* deltaY:100
* },// 平移 x=100, y=100 意味着第一个点实际上会被画在 x=300, y=655
* 'https://cdn.luogu.org/upload/usericon/25512.png',
* draw.taskFromLittleMing
* ]);
*/

/**
* 如果在冬日绘版网页的控制台中运行脚本,则可以使用以下快捷键:
* - F:开关绘版更新。关闭绘版更新可以提高性能,增加后台挂机的稳定性。
* - G: 开关预览显示。有 bug,如果浏览器窗口足够宽(>640 像素),预览会左偏 5 像素。在绘版放大(不是浏览器放大)时预览也是错的 QAQ
*/

});

标签:canvas,task,return,画画,color,rgb,let
来源: https://www.cnblogs.com/wzsyyh/p/12128635.html