Skip to content

Commit

Permalink
replace convert to jpeg options with a generic convert option
Browse files Browse the repository at this point in the history
  • Loading branch information
negrel committed Jan 5, 2024
1 parent a49ec4d commit 82471a5
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 19 deletions.
47 changes: 40 additions & 7 deletions islands/ImagesCompressor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
startCompressing,
updateCompressionOptions,
} from "@/signals/home.ts";
import { CompressionOptions } from "@/lib/compress.ts";

function IdleSection() {
const compressionOptions = getCompressionOptions();
Expand Down Expand Up @@ -55,12 +56,30 @@ function IdleSection() {
disabled: getSelectedImages().length <= 1,
},
}, {
name: "convertToJpeg",
label: "convert to JPEG",
inputCheckbox: {
checked: compressionOptions.convertToJpeg,
onChange: (value: boolean) =>
updateCompressionOptions({ convertToJpeg: value }),
name: "convert",
label: "convert to",
select: {
onChange: (value: CompressionOptions["convert"]) =>
updateCompressionOptions({ convert: value }),
options: [
{
value: "none",
children: "none",
},
{
value: "image/jpeg",
children: "jpg",
},
{
value: "image/png",
children: "png",
},
{
value: "image/webp",
children: "webp",
},
],
value: compressionOptions.convert,
},
}];

Expand All @@ -69,7 +88,7 @@ function IdleSection() {
<details className="cursor-pointer my-6 px-4 select-none">
<summary className="mb-4">Settings</summary>
{settingsInputs.map(
({ name, label, inputRange, inputNumber, inputCheckbox }) => (
({ name, label, inputRange, inputNumber, inputCheckbox, select }) => (
<div className="flex flex-wrap align-center justify-center gap-4 my-4">
<label for={name}>{label}</label>
<div className="flex flex-nowrap align-center justify-center gap-4 text-slate-950">
Expand Down Expand Up @@ -112,6 +131,20 @@ function IdleSection() {
)}
/>
)}
{select && (
<select
className="px-2"
onChange={(ev) =>
select.onChange(
(ev.target as HTMLSelectElement)
.value as CompressionOptions["convert"],
)}
>
{select.options.map((opt) => (
<option value={opt.value}>{opt.children}</option>
))}
</select>
)}
</div>
</div>
),
Expand Down
16 changes: 11 additions & 5 deletions lib/compress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface CompressionOptions {
maxHeight: number;
zipFile: boolean;
hardwareConcurrency: number;
convertToJpeg: boolean;
convert: "image/png" | "image/jpeg" | "image/webp" | "none";
}

interface WorkerCompressOptions {
Expand All @@ -34,8 +34,8 @@ export async function compress(
const zipWriter = new ZipWriter(zipFileWriter);

for (const file of imageFiles) {
const filename = options.convertToJpeg
? replaceExtension(file.name, "jpg")
const filename = options.convert !== "none"
? replaceExtension(file.name, fileTypeToExtension[options.convert])
: file.name;

const p = (async () => {
Expand Down Expand Up @@ -63,7 +63,7 @@ export async function compress(
} else {
// Download images directly.
const blob = new Blob([compressedImg], {
type: options.convertToJpeg ? "image/jpeg" : file.type,
type: options.convert !== "none" ? options.convert : file.type,
});
downloadBlob(blob, filename);
}
Expand All @@ -83,6 +83,12 @@ export async function compress(
return results;
}

const fileTypeToExtension = {
"image/png": "png",
"image/jpeg": "jpg",
"image/webp": "jpg",
};

function downloadBlob(blob: Blob, filename: string) {
const anchor = document.createElement("a");
anchor.href = window.URL.createObjectURL(blob);
Expand All @@ -94,7 +100,7 @@ function replaceExtension(filename: string, ext: string) {
const splitted = filename.split(".");
if (splitted.length === 1) {
splitted.push(ext);
} else if (splitted[splitted.length - 1].toLowerCase() !== "jpg") {
} else if (splitted[splitted.length - 1].toLowerCase() !== ext) {
splitted[splitted.length - 1] = ext;
}

Expand Down
2 changes: 1 addition & 1 deletion signals/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const defaultCompressionOptions: CompressionOptions = {
maxHeight: 4096,
zipFile: true,
hardwareConcurrency: (navigator.hardwareConcurrency ?? 8) / 4,
convertToJpeg: true,
convert: "none",
};

const compressionOptions = signal<CompressionOptions>({
Expand Down
23 changes: 17 additions & 6 deletions static/worker_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,20 @@ function workerProcedureHandler(
};
}

function isLosslessImageFormat(format) {
switch (format) {
case "image/webp", "image/jpeg":
return false;
default:
return true;
}
}

self.onmessage = workerProcedureHandler({
setupWorker(workedId) {
console.debug("worker", workedId, "setup");
},
async compress(imageFile, { quality, maxWidth, maxHeight, convertToJpeg }) {
async compress(imageFile, { quality, maxWidth, maxHeight, convert }) {
let bitmap = await createImageBitmap(imageFile);
let drawWidth = bitmap.width;
let drawHeight = bitmap.height;
Expand All @@ -67,11 +76,13 @@ self.onmessage = workerProcedureHandler({

ctx.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);

// Image is not a JPEG images, apply JPEG encoding to compress it.
if (imageFile.type !== "image/jpeg" && !convertToJpeg) {
const convertToLossless = isLosslessImageFormat(
convert !== "none" ? convert : imageFile.type,
);
if (convertToLossless) {
ctx.drawImage(bitmap, 0, 0, drawWidth, drawHeight);
const blob = await offscreenCanvas.convertToBlob({
type: "image/jpeg",
type: "image/webp",
quality: quality / 100,
});

Expand All @@ -81,8 +92,8 @@ self.onmessage = workerProcedureHandler({
ctx.drawImage(bitmap, 0, 0, drawWidth, drawHeight);

const blob = await offscreenCanvas.convertToBlob({
type: convertToJpeg ? "image/jpeg" : imageFile.type,
quality: quality / 100,
type: convert === "none" ? imageFile.type : convert,
quality: convertToLossless ? undefined : quality / 100,
});

return blob.arrayBuffer();
Expand Down

0 comments on commit 82471a5

Please sign in to comment.