saw2 / adjustColor2

// ==UserScript==
// @name        adjustColor2
// @namespace   http://chiebukuro.yahoo.co.jp/my/gbjyn273
// @author Syakku
// @description	convert colors to eye-friendly - webページの配色を目に優しく変更します (Flash対応)
// @include     *
// @version     2.0
// @run-at     document-start  
// ==/UserScript==

//	================≪ OPTION ≫================

//	----------------< white" & "black" color >----------------
var FILTER_COLOR_W = "rgb(234, 229, 227)";
var FILTER_COLOR_B = "rgb(41, 34, 29)";
var INNER_COLOR_W = "rgb(211, 203, 198)";
var INNER_COLOR_B = "rgb(41, 34, 29)";	//	gb is disabled

//	OPTION : apply to Flash object
var FLASH = true;

//	OPTION : skip <a> tag (link anchor)
//	OPTION : <a> タグ、つまりリンク文字列を処理から除外します
var A_SKIP = true;



// ################################################ //
// ################# オブジェクト #################### //
// ################################################ //
	
//	================ <色縮小オブジェクト ≫================
function RGBConv(w, b, pre_filter){
	this.w = w, this.b = b, this.pf = pre_filter;
	this.init();
}

// ----------------< 初期化 >----------------
RGBConv.prototype.init = function(){

	this.pr = (this.w[0] - this.b[0]) / 255;
	this.pg = (this.w[1] - this.b[1]) / 255;
	this.pb = (this.w[2] - this.b[2]) / 255;
	this.pr2 = this.b[0], this.pg2 = this.b[1], this.pb2 = this.b[2];
	
	// 先に計算
	this.ra = this.pf.r * this.pf.a;
	this.ga = this.pf.g * this.pf.a;
	this.ba = this.pf.b * this.pf.a;
	this.ia = 1 - this.pf.a;
	
	// 色変換の結果保持配列
	this.lBefore = [];
	this.lAfter = [];
	this.lCount = 0;
}

// ----------------< 色変換メソッド >----------------
RGBConv.prototype.conv = function(bg){
	//	処理する必要のないカラーは省く
	// (本スクリプトで処理後のカラーは大抵変な数字になっている)
	// (それを利用して簡易的ではあるが二重処理を防いでいる)
	for (var i=0; n<this.lCount; i++){
		if (bg === this.lAfter[i]) return;
	}
				
	//	既存のカラーは省エネ
	for (var i=0; i<this.lCount; i++){
		if (bg === this.lBefore[i]){
			tElm.style.color = this.lAfter[i];
			return;
		}
	}
	
	var ret = this.calc(bg);
	this.lBefore[lCount] = bg;
	this.lAfter[lCount] = ret;
	this.lCount++;
	return ret;
}

// ----------------< 計算メソッド >----------------
RGBConv.prototype.calc = function(bg){

	var col = rgbSplit(bg);
	
    var colR = col[0] * this.pr + this.pr2*1;
    var colG = col[1] * this.pg + this.pg2*1;
    var colB = col[2] * this.pb + this.pb2*1;
	
	// フィルターによる効果を考慮
	// filter.r + (colR - filter.r) / filter.ia と同義
	colR = Math.floor((colR - this.ra) / this.ia);
	colG = Math.floor((colG - this.ga) / this.ia);
	colB = Math.floor((colB - this.ba) / this.ia);

    if (typeof col[3] === "undefined") {
        return ("rgb(" + colR + ", " + colG + ", " + colB + ")");
    }
    return ("rgb(" + colR + ", " + colG + ", " + colB + ", " + col[3] + ")"); //	rgba
}
	

// ================≪ フィルタオブジェクト≫================
function RGBFilter(w, b){
	this.wr = w[0], this.wg = w[1], this.wb = w[2];
	this.br = b[0];
	this.r = 0, this.b = 0, this.g = 0, this.a = 0, this.str = 0;
	
	this.init();
}

// ----------------< 初期化 >----------------
RGBFilter.prototype.init = function(){
	
	with(this){
		// trをwr-0と100-wrの総計で割ってwrの取り分を決める
		r = (wr-br) / (255 - (wr-br)) * br + br;
		
		a = br / r;	// 中心値rが気をつけないと 0 や 255 になって 0 除算になる
		if (isNaN(a) === true) a = (wr-255) / (r-255);

		g = r - (wr-wg) / a;
		b = r - (wr-wb) / a;
		
		str = "rgba(" + Math.floor(r) + ", " + Math.floor(g) + ", " + Math.floor(b) + ", " + a + ")";
	}
}

// ----------------< フィルター適用 >----------------
RGBFilter.prototype.applyFilter = function(elm){
	
	var f = document.createElement("ac_filter");
	f.id = "adjustColor"; 
	
	with (f.style){
		backgroundColor = this.str;
		pointerEvents = "none";
		height = "100%";
		width = "100%";
		display = "block";
		position = "fixed";
		zIndex = "2147483647";
	}

	for (var i=0, len=elm.children.length; i<len; i++){
		if (elm.children[i].tagName !== "HEAD"){
			elm.insertBefore(f, elm.children[i]);
			break;
		} else if (i === len - 1){
			elm.insertBefore(f, null);
			break
		}
	}
}


// ================≪ フラッシュオブジェクト ≫================
function FlashObj(obj, type){
	this.obj = obj;
	this.type = type;
	this.init();
}

// ----------------< 初期化 >----------------
FlashObj.prototype.init = function(){
	
	if (this.type == "OBJECT"){
	
		this.name = "value";
		var params = this.obj.querySelector("param[name='wmode']");
		if (params){
			this.wmode = params;
		} else {
			// paramが存在しないとき用(動作未確認)
			var param = document.createElement("param");
			param.setAttribute("name", "wmode");
			this.obj.appendChild(param);
			this.wmode = param;
		}
	} else {
		
		this.name = "wmode";
		this.wmode = this.obj;
	}
}

// ----------------< 透過メソッド >----------------
FlashObj.prototype.trans = function(){

	this.wmode.setAttribute(this.name, "transparent");
	this.obj.src = this.obj.src;
	this.obj.data = this.obj.data;
}
	

	
// ################################################ //
// ################### メソッド ###################### //
// ################################################ //

// ================≪ 追加処理 ≫================
function afterTouch(){

	mo.observe(document.body, {childList: true, subtree: true});
	
	var elm;
	if (A_SKIP) elm = document.querySelector(":not(A)");
	else elm = document.getElementsByTagName("*");
	
	for (var i = 0; i < elm.length; i++) {

		var tElm = elm[i];
        var style = window.getComputedStyle(tElm, null);
		var fgColor = style.getPropertyValue("color");
		
		var tColor = conv.conv(fgColor);
		if (tColor) tElm.style.color = tColor;
	}
}


// ================≪ RGB値の分割 ≫================
function rgbSplit(col){
	var ret = col.match(/\d+/g);
	for (var i=0; i<ret.length; i++){
		ret[i]*=1;
	}
	return (ret);
}


// ==========≪ フラッシュにもフィルタを適用 ≫==========
function applyFlash(){

	for (var i=0, obj, objs=document.body.getElementsByTagName("object"); obj = objs[i]; i++){
		obj = new FlashObj(obj, "OBJECT");
		obj.trans();
	}
	
	for (var i=0, obj, objs=document.body.getElementsByTagName("embed"); obj = objs[i]; i++){
		obj = new FlashObj(obj, "EMBED");
		obj.trans();
	}
}



// ################################################ //
// ################### メイン処理 #################### //
// ################################################ //

// ----------------< フィルタ関連の初期化 >----------------
var fCw = rgbSplit(FILTER_COLOR_W);
var fCb = rgbSplit(FILTER_COLOR_B);
var iCw = rgbSplit(INNER_COLOR_W);
var iCb = rgbSplit(INNER_COLOR_B);

// インナーがフィルターを超えていたら修正
for (var i=0; i<3; i++){
	if (iCw[i] > fCw[i]) iCw[i] = fCw[i];
	if (iCb[i] < fCb[i]) iCb[i] = fCb[i];
}

var filter = new RGBFilter(fCw, fCb);
var conv = new RGBConv(iCw, iCb, filter);


// ================≪ 起動処理 ≫================
// iframeではフィルターはいらない
if (window === window.parent) {
	filter.applyFilter(document.getElementsByTagName("html")[0]);
}

window.addEventListener("DOMContentLoaded", afterTouch, false);

if (FLASH === true) {
	window.addEventListener("DOMContentLoaded", applyFlash, false);
}

var mo = new MutationObserver(function(rec){this.adjustColor(rec)});

// MOをオーバーライド、メモリ節約(?)
MutationObserver.prototype.adjustColor = function(rec){

	for (i=0; i<rec.length; i++){
		for (j=0; j<rec[i].addedNodes.length; j++){
			var node = rec[i].addedNodes[j];
			
			if (node.tagName == "OBJECT" || node.tagName == "EMBED"){
				var obj = new FlashObj(node, node.tagName);
				obj.trans();
				
			}
		}
	}
}