yoedge-horizontal-screen

实现灰机汉化组漫画网站(yoedge.com)的横屏阅读模式

// ==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) + '%';
}