Skip to content

Commit 4f7c682

Browse files
committed
Update Picviewer CE+.user.js
1 parent 5fb569f commit 4f7c682

File tree

1 file changed

+242
-6
lines changed

1 file changed

+242
-6
lines changed

Picviewer CE+/Picviewer CE+.user.js

Lines changed: 242 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます
1313
// @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente
1414
// @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения
15-
// @version 2025.12.19.1
15+
// @version 2026.1.5.1
1616
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg==
1717
// @namespace https://github.com/hoothin/UserScripts
1818
// @homepage https://pv.hoothin.com/
@@ -12055,6 +12055,186 @@ ImgOps | https://imgops.com/#b#`;
1205512055
}
1205612056
} else GM_fetch = fetch;
1205712057

12058+
class ImageSizeFetcher {
12059+
constructor(gmXhr, config = {}) {
12060+
if (!gmXhr) throw new Error("GM_xmlhttpRequest is required");
12061+
this.gmXhr = gmXhr;
12062+
this.cache = new Map();
12063+
this.queue = [];
12064+
this.activeCount = 0;
12065+
this.concurrency = config.concurrency || 3;
12066+
this.timeout = config.timeout || 5000;
12067+
}
12068+
12069+
async getSize(url, options = {}) {
12070+
const { force = false, strategy = 'auto' } = options;
12071+
if (!url) return null;
12072+
12073+
if (!force && this.cache.has(url)) {
12074+
return this.cache.get(url);
12075+
}
12076+
12077+
if (url.startsWith('data:')) {
12078+
const size = this._calculateBase64Size(url);
12079+
this.cache.set(url, size);
12080+
return size;
12081+
}
12082+
12083+
if (url.startsWith('blob:')) {
12084+
return this._getBlobSize(url);
12085+
}
12086+
12087+
if (strategy === 'local-only' || strategy === 'auto') {
12088+
const perfSize = this._getFromPerformance(url);
12089+
if (perfSize !== null) {
12090+
this.cache.set(url, perfSize);
12091+
return perfSize;
12092+
}
12093+
if (strategy === 'local-only') return null;
12094+
}
12095+
12096+
return this._scheduleRequest(url);
12097+
}
12098+
12099+
_calculateBase64Size(dataUrl) {
12100+
const base64Str = dataUrl.split(',')[1];
12101+
if (!base64Str) return 0;
12102+
const len = base64Str.length;
12103+
const padding = (base64Str.match(/=/g) || []).length;
12104+
return (len * 3 / 4) - padding;
12105+
}
12106+
12107+
async _getBlobSize(blobUrl) {
12108+
try {
12109+
const response = await fetch(blobUrl);
12110+
const blob = await response.blob();
12111+
return blob.size;
12112+
} catch (e) {
12113+
console.error('Blob fetch failed', e);
12114+
return null;
12115+
}
12116+
}
12117+
12118+
_getFromPerformance(url) {
12119+
const entries = performance.getEntriesByName(url);
12120+
if (entries.length > 0) {
12121+
const entry = entries[entries.length - 1];
12122+
if (entry.decodedBodySize > 0) return entry.decodedBodySize;
12123+
if (entry.encodedBodySize > 0) return entry.encodedBodySize;
12124+
}
12125+
return null;
12126+
}
12127+
12128+
_scheduleRequest(url) {
12129+
return new Promise((resolve, reject) => {
12130+
this.queue.push({ url, resolve, reject });
12131+
this._processQueue();
12132+
});
12133+
}
12134+
12135+
_processQueue() {
12136+
if (this.activeCount >= this.concurrency || this.queue.length === 0) {
12137+
return;
12138+
}
12139+
12140+
this.activeCount++;
12141+
const { url, resolve, reject } = this.queue.shift();
12142+
12143+
this._fetchByHead(url)
12144+
.then(size => {
12145+
if (size !== null) this.cache.set(url, size);
12146+
resolve(size);
12147+
})
12148+
.catch(err => {
12149+
console.warn(`Fetch size failed for ${url}`, err);
12150+
resolve(null);
12151+
})
12152+
.finally(() => {
12153+
this.activeCount--;
12154+
this._processQueue();
12155+
});
12156+
}
12157+
12158+
_fetchByHead(url) {
12159+
return new Promise((resolve, reject) => {
12160+
this.gmXhr({
12161+
method: "HEAD",
12162+
url: url,
12163+
timeout: this.timeout,
12164+
onload: (response) => {
12165+
const lenStr = response.responseHeaders.match(/content-length:\s*(\d+)/i);
12166+
if (lenStr && lenStr[1]) {
12167+
resolve(parseInt(lenStr[1], 10));
12168+
} else {
12169+
resolve(null);
12170+
}
12171+
},
12172+
onerror: (err) => reject(err),
12173+
ontimeout: () => reject(new Error("Timeout"))
12174+
});
12175+
});
12176+
}
12177+
}
12178+
12179+
function formatBytes(bytes) {
12180+
if (bytes === null || typeof bytes === "undefined" || isNaN(bytes) || bytes < 0) return "";
12181+
const units = ["B", "KB", "MB", "GB", "TB"];
12182+
let size = bytes;
12183+
let unitIndex = 0;
12184+
while (size >= 1024 && unitIndex < units.length - 1) {
12185+
size /= 1024;
12186+
unitIndex++;
12187+
}
12188+
const precision = size >= 100 ? 0 : (size >= 10 ? 1 : 2);
12189+
const value = size.toFixed(precision).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1");
12190+
return value + " " + units[unitIndex];
12191+
}
12192+
12193+
function updateGalleryHeaderSize(gallery, img, strategy) {
12194+
if (!gallery || !gallery.eleMaps || !gallery.eleMaps['head-left-img-info-size']) return;
12195+
const sizeSpan = gallery.eleMaps['head-left-img-info-size'];
12196+
sizeSpan.textContent = "";
12197+
sizeSpan.title = "";
12198+
if (!img || img.nodeName !== 'IMG') return;
12199+
const sizeUrl = img.src;
12200+
if (!sizeUrl) return;
12201+
imageSizeFetcher.getSize(sizeUrl, { strategy: strategy || 'auto' }).then(size => {
12202+
if (size === null) return;
12203+
if (gallery.img !== img || img.src !== sizeUrl) return;
12204+
if (img.naturalWidth && img.naturalHeight) {
12205+
gallery.eleMaps['head-left-img-info-resolution'].textContent = img.naturalWidth + " x " + img.naturalHeight;
12206+
}
12207+
const sizeText = formatBytes(size);
12208+
sizeSpan.textContent = "(" + sizeText + ")";
12209+
sizeSpan.title = sizeText;
12210+
});
12211+
}
12212+
12213+
function updateViewmoreSizeLabel(labelNode, baseText, url, strategy, baseTextGetter) {
12214+
if (!labelNode || !url) return;
12215+
const initText = typeof baseTextGetter === "function" ? baseTextGetter() : baseText;
12216+
labelNode.textContent = initText;
12217+
imageSizeFetcher.getSize(url, { strategy: strategy || 'local-only' }).then(size => {
12218+
if (size === null) return;
12219+
const sizeText = formatBytes(size);
12220+
const currentBase = typeof baseTextGetter === "function" ? baseTextGetter() : baseText;
12221+
labelNode.textContent = currentBase + " | " + sizeText;
12222+
});
12223+
}
12224+
12225+
var viewmoreAutoSizeThrottle = { last: 0 };
12226+
function scheduleViewmoreAutoSize(labelNode, baseText, url, baseTextGetter) {
12227+
if (!labelNode || !url) return;
12228+
var now = Date.now();
12229+
var delay = Math.max(0, 800 - (now - viewmoreAutoSizeThrottle.last));
12230+
setTimeout(() => {
12231+
viewmoreAutoSizeThrottle.last = Date.now();
12232+
updateViewmoreSizeLabel(labelNode, baseText, url, 'auto', baseTextGetter);
12233+
}, delay);
12234+
}
12235+
12236+
var imageSizeFetcher = new ImageSizeFetcher(_GM_xmlhttpRequest, { concurrency: 3, timeout: 5000 });
12237+
1205812238
var canvas = document.createElement('CANVAS');
1205912239
if (document.body) {
1206012240
if (canvas.style) canvas.style.display = "none";
@@ -13939,6 +14119,7 @@ ImgOps | https://imgops.com/#b#`;
1393914119
'<span class="pv-gallery-head-float-left">'+
1394014120
'<span title="'+i18n("picInfo")+'" class="pv-gallery-head-left-img-info">'+
1394114121
'<span class="pv-gallery-head-left-img-info-resolution" title="'+i18n("resolution")+'">0 x 0</span>'+
14122+
'<span class="pv-gallery-head-left-img-info-size" title="Size"></span>'+
1394214123
'<span class="pv-gallery-head-left-img-info-count" title="'+i18n("picNum")+'">(1 / 1)</span>'+
1394314124
'<span class="pv-gallery-head-left-img-info-scaling" title="'+i18n("scaleRatio")+'">(100%)</span>'+
1394414125
'<span class="pv-gallery-vertical-align-helper"></span>'+
@@ -14325,6 +14506,7 @@ ImgOps | https://imgops.com/#b#`;
1432514506
'head-left-img-info',
1432614507
'head-left-img-info-description',
1432714508
'head-left-img-info-resolution',
14509+
'head-left-img-info-size',
1432814510
'head-left-img-info-count',
1432914511
'head-left-img-info-scaling',
1433014512

@@ -16289,8 +16471,28 @@ ImgOps | https://imgops.com/#b#`;
1628916471
dlSpan.onclick=clickCb;
1629016472
var topP=document.createElement('p');
1629116473
topP.className="pv-top-banner";
16292-
topP.innerHTML=createHTML(img.naturalWidth+' x '+img.naturalHeight);
16474+
var baseTextGetter = () => (img.naturalWidth + ' x ' + img.naturalHeight);
16475+
var baseText = baseTextGetter();
16476+
topP.textContent = baseText;
16477+
updateViewmoreSizeLabel(topP, baseText, curNode.dataset.src, 'local-only', baseTextGetter);
1629316478
topP.title=dlSpan.title;
16479+
let hoverTimer = null;
16480+
let autoFetched = false;
16481+
let onEnter = () => {
16482+
if (autoFetched || topP.textContent.indexOf("|") !== -1) return;
16483+
hoverTimer = setTimeout(() => {
16484+
autoFetched = true;
16485+
scheduleViewmoreAutoSize(topP, baseText, curNode.dataset.src, baseTextGetter);
16486+
}, 1000);
16487+
};
16488+
let onLeave = () => {
16489+
if (hoverTimer) {
16490+
clearTimeout(hoverTimer);
16491+
hoverTimer = null;
16492+
}
16493+
};
16494+
imgSpan.addEventListener("mouseenter", onEnter, true);
16495+
imgSpan.addEventListener("mouseleave", onLeave, true);
1629416496
var checkBox=document.createElement('input');
1629516497
checkBox.type="checkbox";
1629616498
let self=this;
@@ -17000,6 +17202,7 @@ ImgOps | https://imgops.com/#b#`;
1700017202
this.imgNaturalSize=imgNaturalSize;
1700117203

1700217204
this.eleMaps['head-left-img-info-resolution'].textContent=imgNaturalSize.w + ' x ' + imgNaturalSize.h;
17205+
updateGalleryHeaderSize(this, img, 'auto');
1700317206
var thumbnails=this.eleMaps['sidebar-thumbnails-container'].childNodes,i=0;
1700417207
thumbnails=Array.prototype.slice.call(thumbnails).filter(function(thumbnail){
1700517208
if(thumbnail.style.display=="none"){
@@ -17037,6 +17240,7 @@ ImgOps | https://imgops.com/#b#`;
1703717240
this.imgError=true;
1703817241
this.img.style.display='none';
1703917242
this.eleMaps['img_broken'].style.display='inline-block';
17243+
this.eleMaps['head-left-img-info-size'].textContent='';
1704017244
dataset(relatedThumb,'naturalSize',JSON.stringify({w: 0, h: 0}));
1704117245
}else{
1704217246
var srcs=dataset(relatedThumb, 'srcs');
@@ -17049,6 +17253,7 @@ ImgOps | https://imgops.com/#b#`;
1704917253

1705017254
self.imgNaturalSize=imgNaturalSize;
1705117255
self.eleMaps['head-left-img-info-resolution'].textContent=imgNaturalSize.w + ' x ' + imgNaturalSize.h;
17256+
updateGalleryHeaderSize(self, this, 'auto');
1705217257
dataset(relatedThumb,'naturalSize',JSON.stringify(imgNaturalSize));
1705317258
let key = imgNaturalSize.w + "x" + imgNaturalSize.h;
1705417259
self.sizeMap[key] = (self.sizeMap[key] || 0) + 1;
@@ -17584,6 +17789,7 @@ ImgOps | https://imgops.com/#b#`;
1758417789
//清空dom
1758517790
this.eleMaps['sidebar-thumbnails-container'].innerHTML=createHTML('');
1758617791
this.eleMaps['head-left-img-info-resolution'].textContent='0 x 0';
17792+
this.eleMaps['head-left-img-info-size'].textContent='';
1758717793
this.eleMaps['head-left-img-info-count'].textContent='(1 / 1)';
1758817794
this.eleMaps['head-left-img-info-scaling'].textContent='(100%)';
1758917795
//隐藏滚动条
@@ -18890,6 +19096,10 @@ ImgOps | https://imgops.com/#b#`;
1889019096
.pv-gallery-head-left-img-info{\
1889119097
cursor:help;\
1889219098
}\
19099+
.pv-gallery-head-left-img-info-size {\
19100+
margin-left: 6px;\
19101+
margin-right: 6px;\
19102+
}\
1889319103
.pv-gallery-head-left-img-info-description {\
1889419104
margin-left: 10px;\
1889519105
margin-right: 10px;\
@@ -20624,6 +20834,8 @@ ImgOps | https://imgops.com/#b#`;
2062420834
this.initPos = initPos || false;
2062520835
this.preview = !!preview;
2062620836
this.isImg = this.img.nodeName.toUpperCase() == 'IMG';
20837+
this.imgSizeBytes = null;
20838+
this.imgSizeText = "";
2062720839
this.init();
2062820840
if (data && this.isImg) {
2062920841
this.img.src = location.protocol == "https" ? data.src.replace(/^http:/,"https:") : data.src;
@@ -20838,6 +21050,8 @@ ImgOps | https://imgops.com/#b#`;
2083821050
img.onload = function(e) {
2083921051
if (self.removed) return;
2084021052
self.loaded = true;
21053+
self.imgSizeBytes = null;
21054+
self.imgSizeText = "";
2084121055
container.style.background='';
2084221056
if (self.preview && img.naturalHeight == 1 && img.naturalWidth == 1) {
2084321057
self.remove();
@@ -20849,6 +21063,7 @@ ImgOps | https://imgops.com/#b#`;
2084921063
w:img.naturalWidth,
2085021064
};
2085121065
self.setToolBadge('zoom',self.zoomLevel);
21066+
self.fetchImgSize(self.img.src, 'auto');
2085221067
if (self==uniqueImgWin && prefs.floatBar.globalkeys.previewFollowMouse) {
2085321068
self.followPos(uniqueImgWinInitX, uniqueImgWinInitY);
2085421069
} else {
@@ -20884,7 +21099,8 @@ ImgOps | https://imgops.com/#b#`;
2088421099
}
2088521100
if (imgNaturalSize.h && imgNaturalSize.w) {
2088621101
container.style.background='';
20887-
setSearchState(`<strong>${img.naturalWidth} x ${img.naturalHeight}</strong>`, self.imgState);
21102+
self.updateImgState(1);
21103+
self.fetchImgSize(self.img.src, 'auto');
2088821104
}
2088921105
if (!this.isImg) {
2089021106
if (/^video$/i.test(img.nodeName)) {
@@ -22775,14 +22991,34 @@ ImgOps | https://imgops.com/#b#`;
2277522991
document.addEventListener('mousemove',moveHandler,true);
2277622992
document.addEventListener('mouseup',mouseupHandler,true);
2277722993
},
22994+
updateImgState:function(zoomValue){
22995+
if (!this.imgState || !this.img || !this.img.naturalWidth) return;
22996+
var sizeText = this.imgSizeText ? " (" + this.imgSizeText + ")" : "";
22997+
var zoomText = (typeof zoomValue === "number" && zoomValue !== 1) ? " (" + parseInt(zoomValue * 100) + "%)" : "";
22998+
var indexText = (this.curIndex >= 0 && this.data && this.data.all) ? " <b>[" + (this.curIndex + 1) + "/" + this.data.all.length + "]</b>" : "";
22999+
setSearchState("<strong>" + this.img.naturalWidth + " x " + this.img.naturalHeight + "</strong>" + sizeText + zoomText + indexText, this.imgState);
23000+
this.descriptionSpan && this.imgState.appendChild(this.descriptionSpan);
23001+
},
23002+
fetchImgSize:function(url, strategy){
23003+
if (!this.isImg) return;
23004+
var sizeUrl = url || (this.img ? this.img.src : "");
23005+
if (!sizeUrl) return;
23006+
var self = this;
23007+
imageSizeFetcher.getSize(sizeUrl, { strategy: strategy || 'auto' }).then(size => {
23008+
if (size === null) return;
23009+
if (!self.img || self.img.src !== sizeUrl) return;
23010+
self.imgSizeBytes = size;
23011+
self.imgSizeText = formatBytes(size);
23012+
self.updateImgState(self.zoomLevel || 1);
23013+
});
23014+
},
2277823015
setToolBadge:function(tool,content){
2277923016
var scale=0;
2278023017
switch(tool){
2278123018
case 'zoom':{
2278223019
scale=2;
2278323020
if (this.img.naturalWidth) {
22784-
setSearchState(`<strong>${this.img.naturalWidth} x ${this.img.naturalHeight}</strong>` + (content !== 1 ? ` (${parseInt(content * 100)}%)` : "") + (this.curIndex >=0 ? ` <b>[${this.curIndex + 1}/${this.data.all.length}]</b>` : ""), this.imgState);
22785-
this.descriptionSpan && this.imgState.appendChild(this.descriptionSpan);
23021+
this.updateImgState(content);
2278623022
}
2278723023
}break;
2278823024
case 'rotate':{
@@ -27778,4 +28014,4 @@ ImgOps | https://imgops.com/#b#`;
2777828014
init2();
2777928015
}
2778028016

27781-
})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow));
28017+
})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow));

0 commit comments

Comments
 (0)