Skip to content

Commit bd6fb0a

Browse files
committed
New: Support for JXL image format
1 parent 240efb1 commit bd6fb0a

7 files changed

Lines changed: 129 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1111
- Improve chapter detection in tracking [`ad05a23`](https://github.com/ollm/OpenComic/commit/ad05a2364882227688cccfa2fdf58a21f8e9615c)
1212
- Support for `JP2` image format [`e11046b`](https://github.com/ollm/OpenComic/commit/e11046b4eca758f8b15c1caeb831a235a5f3d62c)
1313
- Support for `JXR` image format [`0e24922`](https://github.com/ollm/OpenComic/commit/0e2492264e1dbc94126c7d79e3e3c50fc426846c)
14+
- Support for `JXL` image format
1415

1516
## [v1.4.1](https://github.com/ollm/OpenComic/releases/tag/v1.4.1) (08-02-2025)
1617

package-lock.json

Lines changed: 29 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@
9090
"jpegxr": "^0.3.0",
9191
"jquery": "^3.7.1",
9292
"jquery-bez": "^1.0.11",
93+
"jxl-oxide-wasm": "^0.11.1",
9394
"lodash.isequal": "^4.5.0",
94-
"mime": "^3.0.0",
95+
"mime": "^4.0.6",
9596
"mv": "^2.1.1",
9697
"node-7z": "^3.0.0",
9798
"node-scp": "^0.0.23",
@@ -134,6 +135,8 @@
134135
"node_modules/7zip-bin/${os}/${arch}",
135136
"node_modules/pdfjs-dist/build/pdf.mjs",
136137
"node_modules/pdfjs-dist/build/pdf.worker.mjs",
138+
"node_modules/mime",
139+
"node_modules/jxl-oxide-wasm",
137140
"images/icon-border-transparent.png",
138141
"images/logo.png",
139142
"images/logo.svg",

scripts/opencomic.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ const electron = require('electron'),
7979
hb = require('handlebars'),
8080
os = require('os'),
8181
ejs = require('electron-json-storage'),
82-
mime = require('mime'),
8382
sha1 = require('sha1'),
8483
p = require('path'),
8584
isEqual = require('lodash.isequal'),
@@ -143,6 +142,7 @@ var compatibleMime = [
143142
'image/jpm',
144143
'image/jxr',
145144
'image/vnd.ms-photo',
145+
'image/jxl',
146146
'image/png',
147147
'image/apng',
148148
'image/svg',
@@ -166,6 +166,8 @@ var convertMime = [
166166
// jxr
167167
'image/jxr',
168168
'image/vnd.ms-photo',
169+
// jxl
170+
'image/jxl',
169171
];
170172

171173
var compressedMime = {
@@ -279,6 +281,10 @@ var imageExtensions = {
279281
'j2c',
280282
'jpc',
281283
'jpx',
284+
'jxr',
285+
'hdp',
286+
'wdp',
287+
'jxl',
282288
'png',
283289
'x-png',
284290
'apng',
@@ -306,6 +312,8 @@ var imageExtensions = {
306312
'jxr',
307313
'hdp',
308314
'wdp',
315+
// jxl
316+
'jxl',
309317
],
310318
jpg: [
311319
'jpg',
@@ -331,6 +339,9 @@ var imageExtensions = {
331339
'hdp',
332340
'wdp',
333341
],
342+
jxl: [
343+
'jxl',
344+
],
334345
png: [
335346
'png',
336347
'x-png',
@@ -471,6 +482,8 @@ window.onload = function() {
471482

472483
async function startApp()
473484
{
485+
await loadMime();
486+
474487
if(config.checkReleases)
475488
checkReleases.check();
476489

@@ -644,6 +657,18 @@ async function loadPdfjs()
644657
return true;
645658
}
646659

660+
var mime = false;
661+
662+
async function loadMime()
663+
{
664+
if(mime) return;
665+
666+
mime = await import(asarToAsarUnpacked(p.join(appDir, 'node_modules/mime/dist/src/index.js')));
667+
mime = mime.default;
668+
669+
return true;
670+
}
671+
647672
async function loadWebdav()
648673
{
649674
let webdav = await import(asarToAsarUnpacked(p.join(appDir, 'node_modules/webdav/dist/web/index.js')));

scripts/worker.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ async function loadPdfjs()
2626
return true;
2727
}
2828

29+
JxlImage = false;
30+
31+
async function loadJxlImage()
32+
{
33+
if(JxlImage) return;
34+
35+
JxlImage = await import(asarToAsarUnpacked(p.join(__dirname, '..', 'node_modules/jxl-oxide-wasm/jxl_oxide_wasm.js')));
36+
37+
await JxlImage.default();
38+
JxlImage = JxlImage.JxlImage;
39+
40+
return true;
41+
}
42+
2943
self.addEventListener('message', async function(message) {
3044

3145
const data = message.data;

scripts/worker/convert-image.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,36 @@ async function jxr(path)
8181
return false;
8282
}
8383

84+
async function jxl(path)
85+
{
86+
const buffer = fs.readFileSync(path);
87+
88+
if(JxlImage === false)
89+
await loadJxlImage();
90+
91+
try
92+
{
93+
const image = new JxlImage();
94+
image.feedBytes(buffer);
95+
96+
if(!image.tryInit())
97+
throw new Error('Partial image, no frame data');
98+
99+
const renderResult = image.render();
100+
const png = renderResult.encodeToPng();
101+
102+
return {
103+
png: png,
104+
};
105+
}
106+
catch(error)
107+
{
108+
return {error: error};
109+
}
110+
111+
return false;
112+
}
113+
84114
async function convert(path, mime)
85115
{
86116
switch (mime)
@@ -97,6 +127,11 @@ async function convert(path, mime)
97127

98128
return jxr(path);
99129

130+
break;
131+
case 'image/jxl':
132+
133+
return jxl(path);
134+
100135
break;
101136
}
102137

scripts/workers.js

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,13 @@ function convertBuffer(buffer, bits, bitsString = false)
186186

187187
async function convertImage(path)
188188
{
189-
const webpPath = fileManager.realPath(path);
189+
const pngPath = fileManager.realPath(path);
190190
const realPath = fileManager.realPath(path, -1);
191191

192-
fileManager.setTmpUsage(webpPath);
192+
fileManager.setTmpUsage(pngPath);
193193

194-
if(fs.existsSync(webpPath))
195-
return webpPath;
194+
if(fs.existsSync(pngPath))
195+
return pngPath;
196196

197197
const result = await work({
198198
job: 'convertImage',
@@ -201,30 +201,38 @@ async function convertImage(path)
201201
mime: mime.getType(realPath),
202202
});
203203

204-
if(result.buffer)
205-
{
206-
const parentPath = p.dirname(webpPath);
204+
const parentPath = p.dirname(pngPath);
207205

208-
if(!fs.existsSync(parentPath))
209-
fs.mkdirSync(parentPath);
206+
if(!fs.existsSync(parentPath))
207+
fs.mkdirSync(parentPath);
210208

209+
if(result.buffer)
210+
{
211211
const bits = result.bits;
212212

213213
if(bits > 8 && result.buffer instanceof Uint8Array)
214214
result.buffer = convertBuffer(result.buffer, bits, result.bitsString);
215215

216-
await image.rawToPng(result.buffer, webpPath, {
216+
const raw = {
217217
width: result.width,
218218
height: result.height,
219219
channels: result.channels,
220220
rgb16: bits > 8 ? true : false,
221221
premultiplied: result.premultiplied || false,
222-
}, {
222+
};
223+
224+
await image.rawToPng(result.buffer, pngPath, raw, {
223225
compressionLevel: 2,
224226
removeAlpha: result.removeAlpha,
225227
});
226228

227-
return webpPath;
229+
return pngPath;
230+
}
231+
else if(result.png && result.png instanceof Uint8Array)
232+
{
233+
fs.writeFileSync(pngPath, Buffer.from(result.png), function(){});
234+
235+
return pngPath;
228236
}
229237

230238
if(result.error)

0 commit comments

Comments
 (0)