Greasy Fork is available in English.
Bilingual UI, AI 2x Sharpen, Source Finder, and VIVID Color Palette (Prioritizes accent colors like green patina).
当前为
// ==UserScript==
// @name Pinterest Ultra Assistant V6.4 (Vivid Color Engine)
// @namespace http://tampermonkey.net/
// @version 6.4
// @description Bilingual UI, AI 2x Sharpen, Source Finder, and VIVID Color Palette (Prioritizes accent colors like green patina).
// @author Pi Xiao
// @match https://*.pinterest.com/*
// @grant GM_openInTab
// @grant GM_setClipboard
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const BRIDGE_PAGE = "https://meishubiji.cn/ai-processing-center/";
// --- 算法:颜色感知差异度 ---
function getColorDist(hex1, hex2) {
const r1 = parseInt(hex1.slice(1,3), 16), g1 = parseInt(hex1.slice(3,5), 16), b1 = parseInt(hex1.slice(5,7), 16);
const r2 = parseInt(hex2.slice(1,3), 16), g2 = parseInt(hex2.slice(3,5), 16), b2 = parseInt(hex2.slice(5,7), 16);
// 使用简单的加权欧式距离,更符合人眼对色彩的感知
return Math.sqrt((r1-r2)**2 * 0.3 + (g1-g2)**2 * 0.59 + (b1-b2)**2 * 0.11);
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
}
// --- 核心:提取灵魂色盘 ---
function getVividPalette(img) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 100; canvas.height = 100; // 提高采样精度
ctx.drawImage(img, 0, 0, 100, 100);
const data = ctx.getImageData(0, 0, 100, 100).data;
let colorMap = {};
for (let i = 0; i < data.length; i += 12) { // 步进采样
const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
if (a < 128) continue;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
const saturation = max - min; // 饱和度计算
const brightness = (max + min) / 2;
// 核心逻辑:如果颜色太暗(接近黑)或太亮(接近白)或太灰(低饱和度),则大幅扣分
let bias = 1;
if (saturation < 30) bias = 0.1; // 灰色区域权重极低
if (brightness < 40 || brightness > 220) bias = 0.2; // 极暗或极亮权重降低
const hex = rgbToHex(r, g, b);
// 权重 = 出现频率 * 饱和度的平方 * 偏差修正
colorMap[hex] = (colorMap[hex] || 0) + (Math.pow(saturation, 2) * bias + 1);
}
const sortedColors = Object.keys(colorMap).sort((a, b) => colorMap[b] - colorMap[a]);
const finalPalette = [];
for (const color of sortedColors) {
if (finalPalette.length >= 6) break;
// 确保颜色多样性:新颜色必须与已有颜色有显著差异
if (finalPalette.every(c => getColorDist(c, color) > 50)) {
finalPalette.push(color);
}
}
// 如果抓出来的颜色不足6个(比如纯色图),再用普通排序补齐
if (finalPalette.length < 6) {
for (const color of sortedColors) {
if (finalPalette.length >= 6) break;
if (!finalPalette.includes(color)) finalPalette.push(color);
}
}
return finalPalette;
}
// --- 智能预览窗 ---
async function processAndShow(imgUrl) {
const originalUrl = imgUrl.replace(/\/(236x|474x|564x|736x|1200x)\//, '/originals/').replace(/\.webp$/, '.jpg');
const overlay = document.createElement('div');
overlay.id = "px-overlay";
overlay.style = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.97);z-index:2147483647;display:flex;flex-direction:column;align-items:center;justify-content:center;color:white;font-family:sans-serif;cursor:zoom-out;";
overlay.innerHTML = '<div style="text-align:center;"><div class="px-spinner"></div><div style="margin-top:15px; font-size:14px; color:#00ffcc; letter-spacing:1px; font-weight:bold;">EXTRACTING SOUL COLORS...</div></div><style>.px-spinner { width:35px; height:35px; border:3px solid rgba(0,255,204,0.1); border-top-color:#00ffcc; border-radius:50%; animation:spin 0.8s linear infinite; margin:0 auto; } @keyframes spin { to { transform:rotate(360deg); } } .px-color:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(255,255,255,0.3); }</style>';
overlay.onclick = () => overlay.remove();
document.body.appendChild(overlay);
const img = new Image();
img.crossOrigin = "Anonymous";
img.src = originalUrl;
img.onload = function() {
const palette = getVividPalette(img);
overlay.innerHTML = "";
const container = document.createElement('div');
container.style = "text-align:center; width:95%; height:90vh; display:flex; flex-direction:column; cursor:default;";
container.onclick = (e) => e.stopPropagation();
const scrollBox = document.createElement('div');
scrollBox.style = "overflow:auto; border:1px solid #333; border-radius:12px; flex:1; background:#050505; display:flex; align-items:center; justify-content:center; box-shadow: inset 0 0 100px rgba(0,0,0,1);";
const pImg = document.createElement('img');
pImg.src = originalUrl;
pImg.style = "max-width:200%; image-rendering:-webkit-optimize-contrast; filter:contrast(1.08) saturate(1.05);";
scrollBox.appendChild(pImg);
const bar = document.createElement('div');
bar.style = "padding:30px; display:flex; flex-direction:column; align-items:center; gap:25px; background:#0a0a0a;";
// 色盘生成
let paletteHTML = `<div style="display:flex; gap:15px; align-items:center;">
<span style="font-size:11px; color:#444; text-transform:uppercase; letter-spacing:2px; font-weight:bold;">The Soul Palette:</span>`;
palette.forEach(color => {
paletteHTML += `<div class="px-color" style="width:32px; height:32px; background:${color}; border-radius:8px; cursor:pointer; border:2px solid #222; transition:0.3s;" title="Copy Hex: ${color}" data-hex="${color}"></div>`;
});
paletteHTML += `</div>`;
const actionHTML = `
<div style="display:flex; gap:25px; align-items:center;">
<button id="btn-ai-go" style="background:linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color:white; border:none; padding:16px 50px; border-radius:50px; font-size:16px; font-weight:bold; cursor:pointer; box-shadow:0 10px 30px rgba(37,117,252,0.4); transition:0.3s;">🚀 LAUNCH AI 8K ENGINE</button>
<span style="color:#333; cursor:pointer; font-size:12px; font-weight:bold;" onclick="document.getElementById('px-overlay').remove()">CLOSE PREVIEW ×</span>
</div>
`;
bar.innerHTML = paletteHTML + actionHTML;
container.appendChild(scrollBox);
container.appendChild(bar);
overlay.appendChild(container);
overlay.querySelectorAll('.px-color').forEach(el => {
el.onclick = () => {
const hex = el.getAttribute('data-hex');
GM_setClipboard(hex);
el.style.borderColor = "#fff";
setTimeout(() => el.style.borderColor = "#222", 500);
};
});
overlay.querySelector('#btn-ai-go').onclick = () => window.open(`${BRIDGE_PAGE}?url=${encodeURIComponent(originalUrl)}`, '_blank');
};
img.onerror = () => { window.open(originalUrl, '_blank'); overlay.remove(); };
}
// --- 注入逻辑 ---
function injectButtons() {
const images = document.querySelectorAll('img');
images.forEach(img => {
const src = img.src;
if (!src || !src.includes('pinimg.com') || img.width < 150 || img.closest('.px-helper-bar')) return;
const container = img.closest('[data-test-id="pin-visual-wrapper"]') || img.closest('[data-test-id="visual-content-container"]') || img.parentElement;
if (container) {
if (window.getComputedStyle(container).position === 'static') container.style.position = 'relative';
const bar = document.createElement('div');
bar.className = 'px-helper-bar';
bar.style = "position:absolute; top:10px; left:10px; z-index:1000; display:flex; gap:4px; opacity:0; transition:0.3s; pointer-events:auto;";
container.addEventListener('mouseenter', () => bar.style.opacity = "1");
container.addEventListener('mouseleave', () => bar.style.opacity = "0");
const btnStyle = 'color:white; border:none; border-radius:4px; cursor:pointer; padding:4px 8px; font-weight:bold; font-size:9px; box-shadow:0 2px 5px rgba(0,0,0,0.3); white-space:nowrap;';
const b1 = document.createElement('button'); b1.innerHTML = '🪄 2x HD'; b1.style = btnStyle + 'background:#00BFFF;';
b1.onclick = (e) => { e.preventDefault(); e.stopPropagation(); processAndShow(src); };
const b2 = document.createElement('button'); b2.innerHTML = '🖼️ Originals'; b2.style = btnStyle + 'background:#E60023;';
b2.onclick = (e) => { e.preventDefault(); e.stopPropagation(); window.open(src.replace(/\/(236x|474x|564x|736x)\//, '/originals/'), '_blank'); };
const b3 = document.createElement('button'); b3.innerHTML = '🔍 Source'; b3.style = btnStyle + 'background:#34a853;';
b3.onclick = (e) => { e.preventDefault(); e.stopPropagation(); window.open(`https://lens.google.com/uploadbyurl?url=${encodeURIComponent(src.replace(/\/(236x|474x|564x|736x)\//, '/originals/'))}`, '_blank'); };
bar.appendChild(b1); bar.appendChild(b2); bar.appendChild(b3);
container.appendChild(bar);
}
});
}
setInterval(injectButtons, 2500);
const observer = new MutationObserver(injectButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();