// ==UserScript==
// @name yoedge-horizontal-screen
// @namespace https://github.com/Lockvictor
// @author Lockvictor
// @description 实现灰机汉化组漫画网站(yoedge.com)的横屏阅读模式
// @homepage https://github.com/Lockvictor/yoedge-horizontal-screen
// @match http://*.yoedge.com/smp-app/*
// @version 2.1.3
// @grant none
// ==/UserScript==
//漫画宽高比
var MANGA_ASPECT_RATIO = 1.5;
//默认屏幕占比
var DEFAULT_SCALE_RATIO = 0.6;
//最大占比,漫画与屏幕等宽
var MAX_SCALE_RATIO = 1;
//最小占比,漫画与屏幕等高,即原来的竖屏模式
var MIN_SCALE_RATIO = window.screen.height / MANGA_ASPECT_RATIO / window.screen.width;
//屏幕占比缩放步长
var SCALE_STEP = 0.05;
//j和k快捷键滚动屏幕的比例
var SCROLLBY_RATIO = 0.2;
//漫画当前屏幕占比
var gMangaAreaRatio = DEFAULT_SCALE_RATIO;
(function () {
'use strict';
document.body.innerHTML = '';
document.addEventListener('contextmenu', function (event) {
event.preventDefault();
});
//获取本话的配置信息,主要包括页数和图片的url
var configString = get("smp_cfg.json");
loadAllPage(JSON.parse(configString));
})();
function get(url) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", url, false);
xmlHttp.send(null);
return xmlHttp.responseText;
}
function loadAllPage(config) {
//获取图片url和序号
var orderList = config.pages.order;
var imageUrlList = config.pages.page;
//创建漫画原图的img元素
for (var i = 0; i < orderList.length; i++) {
var order = orderList[i];
var img = document.createElement('img');
img.id = 'img' + order;
img.style.display = 'none';
img.src = imageUrlList[order];
document.body.appendChild(img);
}
var imgElementList = document.getElementsByTagName('img');
//图片传输需要时间,直接获取会出错,采用分次加载,每次加载5张图
var loadStartIndex = 0;
var loadEndIndex = 0;
var LOAD_STEP = 5;
var INDEX_LIMIT = imgElementList.length;
// console.log(`INDEX_LIMIT: ${INDEX_LIMIT}`);
var imgSizeCheckFlag = setInterval(function () {
loadEndIndex = ((loadStartIndex + LOAD_STEP) > INDEX_LIMIT) ? INDEX_LIMIT : (loadStartIndex + LOAD_STEP);
if (isImageOk(imgElementList, loadStartIndex, loadEndIndex)) {
//用canvas绘制原图,实现反色块的处理
drawImageByCanvas(orderList, imageUrlList, loadStartIndex, loadEndIndex);
loadStartIndex = loadEndIndex;
}
if (loadStartIndex === INDEX_LIMIT) {
clearInterval(imgSizeCheckFlag);
for (var i = 0; i < orderList.length; i++) {
var img = document.getElementById('img' + orderList[i]);
document.body.removeChild(img);
}
imgElementList = null;
addNextEpisodeButton();
}
// console.log('loadStartIndex: ' + loadStartIndex);
}, 2000);
// 自定义缩放、滚动等快捷键
customizeShortcut();
}
function isImageOk(imgElementList, startIndex, endIndex) {
var isLoaded = true;
for (var i = startIndex; i < endIndex; i++) {
var img = imgElementList[i];
if (!img.complete || img.naturalWidth === 0) {
isLoaded = false;
break;
}
}
return isLoaded;
}
function drawImageByCanvas(orderList, imageUrlList, startIndex, endIndex) {
for (var i = startIndex; i < endIndex; i++) {
var order = orderList[i];
var img = document.getElementById('img' + order);
var width = img.naturalWidth;
var height = img.naturalHeight;
// console.log(img);
// console.log(`order: ${order}, width: ${width}, height: ${height}`);
var mgcanv = document.createElement('canvas');
mgcanv.id = 'canvas' + order;
mgcanv.width = width;
mgcanv.height = height;
mgcanv.style.display = 'block';
mgcanv.style.margin = '0 auto';
mgcanv.style.width = numberToPercentage(gMangaAreaRatio);
// console.log(mgcanv);
var context = mgcanv.getContext('2d');
context.drawImage(img, 0, 0);
//前5张图没有反色块
if (i > 4) {
cleanClutter(imageUrlList[order], context, width, height);
}
document.body.appendChild(mgcanv);
// console.log(mgcanv);
}
}
function cleanClutter(imageUrl, context, width, height) {
if (imageUrl && window.clutter && imageUrl.indexOf("pages") != -1 && width > 200 && height > 300 && (
(imageUrl.indexOf('.png') != -1 && window.clutter == '1') ||
(imageUrl.indexOf('.png') != -1 && window.clutter == '2' && smp.controller.type == '0' && imageUrl.match(/\/0{0,3}[1aA]\.png$/) !== null) ||
(imageUrl.indexOf('.png') != -1 && window.clutter == '2' && smp.controller.type == '1' && imageUrl.match(/\/0?00[012345]\.png$/) === null) ||
(imageUrl.indexOf('.jpg') != -1 && window.clutter == '2' && smp.controller.type == '1' && imageUrl.match(/\/0?00[012345]\.jpg$/) === null))) {
var imageData = context.getImageData(0, 0, width, height);
var data = imageData.data;
var j1 = 0, j2 = 0;
var r = width * 4, r1 = r * 110, r2 = r;
var y1 = Math.floor(height >> 1) & 0xFFFFFFE;
if (window.clutter == '2' && imageUrl.indexOf('.png') !== -1) {
y1 = 100;
}
var x1 = Math.floor(width / 3);
var x2 = x1 << 1;
var y2 = y1 + 20;
if (height > 550) {
y2 = y1 + 60;
}
if (window.clutter == '2') {
var lastOf = imageUrl.lastIndexOf('/');
var fileName = decodeURI(imageUrl.substring(lastOf));
if (fileName.indexOf("3") != -1) {
y1 -= 8;
y2 -= 11;
} else if (fileName.indexOf("4") != -1) {
y1 -= 6;
y2 -= 13;
} else if (fileName.indexOf("5") != -1) {
y2 -= 14;
} else if (fileName.indexOf("7") != -1) {
y1 -= 2;
y2 -= 3;
} else if (fileName.indexOf("9") != -1) {
y2 -= 13;
} else if (fileName.indexOf("01") != -1) {
y2 -= 11;
} else if (fileName.indexOf("02") != -1) {
y1 -= 12;
y2 -= 17;
}
if (fileName.indexOf("4") != -1) {
x1 -= 7;
x2 -= 19;
} else if (fileName.indexOf("5") != -1) {
x1 -= 15;
x2 -= 13;
} else if (fileName.indexOf("6") != -1) {
x1 -= 31;
x2 = x1 + 76;
} else if (fileName.indexOf("7") != -1) {
x1 += 21;
} else if (fileName.indexOf("8") != -1) {
x2 -= 13;
} else if (fileName.indexOf("01") != -1) {
x2 = x1 + 99;
} else if (fileName.indexOf("02") != -1) {
x1 -= 13;
x2 = x1 + 97;
} else if (fileName.indexOf("03") != -1) {
x2 = x1 + 91;
}
}
var is = y1 * r + x1 * 4, i = 0;
if (window.clutter == '2') {
for (j1 = y1; j1 < y2; j1 += 1) {
i = is;
for (j2 = x1; j2 < x2; j2++) {
data[i + 2] = data[i + 2] ^ 0xFF;
data[i + 1] = data[i + 1] ^ 0xFF;
data[i] = data[i] ^ 0xFF;
i += 4;
}
x2--;
is += r2;
}
} else {
for (j1 = y1; j1 < y2; j1 += 1) {
i = is;
for (j2 = x1; j2 < x2; j2++) {
if (data[i + 3 - r1] !== 0) {
data[i + 2] = data[i + 2] ^ data[i + 2 - r1];
data[i + 1] = data[i + 1] ^ data[i + 1 - r1];
data[i] = data[i] ^ data[i - r1];
}
i += 4;
}
is += r2;
}
}
context.putImageData(imageData, 0, 0);
}
}
function addNextEpisodeButton() {
var style = document.createElement('style');
style.innerHTML = "#next-episode { display: block; margin: 0 auto; height: 70px; background: rgba(211, 211, 211, 0.3) url('res/common/pre.png') no-repeat 50% 14px;}";
style.innerHTML += "#next-episode:hover { background-color: rgba(211, 211, 211, 0.5); }";
document.head.appendChild(style);
var nextEpisodeButton = document.createElement('div');
nextEpisodeButton.id = 'next-episode';
nextEpisodeButton.style.width = numberToPercentage(gMangaAreaRatio);
nextEpisodeButton.addEventListener('click', function (event) {
smp.toolbar.nextApp();
});
document.body.appendChild(nextEpisodeButton);
}
function customizeShortcut() {
// 漫画缩放、滚动、翻页
// 缩放和滚动都可以持续响应,注册到keydown
document.addEventListener('keydown', function (event) {
switch (event.key) {
case '=':
scale(SCALE_STEP);
break;
case '-':
scale(-SCALE_STEP);
break;
case '0':
gMangaAreaRatio = DEFAULT_SCALE_RATIO;
scale(0);
break;
case 'j':
smoothyScrollBy(0, screen.width * gMangaAreaRatio * MANGA_ASPECT_RATIO * SCROLLBY_RATIO);
break;
case 'k':
smoothyScrollBy(0, -screen.width * gMangaAreaRatio * MANGA_ASPECT_RATIO * SCROLLBY_RATIO);
break;
case 'h':
smoothyScrollTo(0, 0);
break;
case 'l':
smoothyScrollTo(0, document.body.scrollHeight);
break;
default:
break;
}
});
// 跳转下一话是单次响应,注册到keyup
document.addEventListener('keyup', function (event) {
switch (event.key) {
case 'n':
smp.toolbar.nextApp();
break;
default:
break;
}
});
}
function scale(increment) {
var expectedScaleRatio = gMangaAreaRatio + increment;
if (increment > 0) {
gMangaAreaRatio = (expectedScaleRatio >= MAX_SCALE_RATIO) ? MAX_SCALE_RATIO : expectedScaleRatio;
} else if (increment < 0) {
gMangaAreaRatio = (expectedScaleRatio <= MIN_SCALE_RATIO) ? MIN_SCALE_RATIO : expectedScaleRatio;
}
var newWidth = numberToPercentage(gMangaAreaRatio);
canvasList = document.getElementsByTagName('canvas');
for (var i = 0; i < canvasList.length; i++) {
canvasList[i].style.width = newWidth;
}
document.getElementById('next-episode').style.width = newWidth;
}
//=====================基础功能相关函数=====================
function smoothyScrollBy(offsetX, offsetY) {
try {
window.scrollBy({ top: offsetY, left: offsetX, behavior: 'smooth' });
} catch (error) {
window.scrollBy(offsetX, offsetY);
}
}
function smoothyScrollTo(x, y) {
try {
window.scrollTo({ top: y, left: x, behavior: 'smooth' });
} catch (error) {
window.scrollTo(x, y);
}
}
function numberToPercentage(value) {
return Math.floor(100 * value) + '%';
}