diff --git a/deno.jsonc b/deno.jsonc index d646c9d..4a712ec 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -19,7 +19,6 @@ "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.1.2", "@hey-api/openapi-ts": "npm:@hey-api/openapi-ts@^0.97.0", "@hey-api/vite-plugin": "npm:@hey-api/vite-plugin@^0.3.1", - "@imagemagick/magick-wasm": "npm:@imagemagick/magick-wasm@^0.0.40", "@kysely/kysely": "jsr:@kysely/kysely@^0.28.16", "@preact/signals": "npm:@preact/signals@^2.9.0", "@std/async": "jsr:@std/async@^1.3.0", @@ -27,11 +26,11 @@ "@std/http": "jsr:@std/http@^1.1.0", "@subframe7536/iconv-lite": "npm:@subframe7536/iconv-lite@^0.8.1", "fresh": "jsr:@fresh/core@^2.3.3", + "image-size": "npm:image-size@^2.0.2", "jose": "npm:jose@^6.2.3", "mysql2": "npm:mysql2@^3.22.3", "preact": "npm:preact@^10.29.1", - "vite": "npm:vite@^7.3.2", - "vite-plugin-static-copy": "npm:vite-plugin-static-copy@^4.1.0" + "vite": "npm:vite@^7.3.2" }, "lint": { "rules": { diff --git a/deno.lock b/deno.lock index f201030..909f40b 100644 --- a/deno.lock +++ b/deno.lock @@ -50,7 +50,6 @@ "npm:@hey-api/openapi-ts@*": "0.97.0_typescript@6.0.3", "npm:@hey-api/openapi-ts@0.97": "0.97.0_typescript@6.0.3", "npm:@hey-api/vite-plugin@~0.3.1": "0.3.1_@hey-api+openapi-ts@0.97.0__typescript@6.0.3_vite@7.3.3", - "npm:@imagemagick/magick-wasm@^0.0.40": "0.0.40", "npm:@opentelemetry/api@^1.9.0": "1.9.1", "npm:@preact/signals@^2.5.1": "2.9.0_preact@10.29.1", "npm:@preact/signals@^2.9.0": "2.9.0_preact@10.29.1", @@ -61,6 +60,7 @@ "npm:esbuild-wasm@~0.25.11": "0.25.12", "npm:esbuild@0.25.7": "0.25.7", "npm:esbuild@~0.25.5": "0.25.7", + "npm:image-size@^2.0.2": "2.0.2", "npm:jose@^6.2.3": "6.2.3", "npm:mysql2@^3.22.3": "3.22.3_@types+node@25.6.0", "npm:openapi-ts@*": "0.3.4_openapi-types@12.1.3", @@ -68,7 +68,6 @@ "npm:preact@^10.28.2": "10.29.1", "npm:preact@^10.29.1": "10.29.1", "npm:rollup@^4.55.1": "4.60.3", - "npm:vite-plugin-static-copy@^4.1.0": "4.1.0_vite@7.3.3", "npm:vite@^7.1.4": "7.3.3", "npm:vite@^7.3.2": "7.3.3" }, @@ -827,9 +826,6 @@ "vite" ] }, - "@imagemagick/magick-wasm@0.0.40": { - "integrity": "sha512-vwV4qZ5JfPblYoJTBKn/dAMElXMmIf4inEsCtjskZwU3609ejpz1pvXWE3UEt8G+0L2SYHdoBNKCPU4ezpDXaQ==" - }, "@jridgewell/gen-mapping@0.3.13": { "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dependencies": [ @@ -1112,13 +1108,6 @@ "ansi-colors@4.1.3": { "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" }, - "anymatch@3.1.3": { - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": [ - "normalize-path", - "picomatch@2.3.2" - ] - }, "argparse@2.0.1": { "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, @@ -1129,15 +1118,6 @@ "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", "bin": true }, - "binary-extensions@2.3.0": { - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" - }, - "braces@3.0.3": { - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": [ - "fill-range" - ] - }, "browserslist@4.28.2": { "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dependencies": [ @@ -1158,7 +1138,7 @@ "c12@3.3.4": { "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", "dependencies": [ - "chokidar@5.0.0", + "chokidar", "confbox", "defu", "dotenv", @@ -1178,25 +1158,10 @@ "caniuse-lite@1.0.30001792": { "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==" }, - "chokidar@3.6.0": { - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": [ - "anymatch", - "braces", - "glob-parent", - "is-binary-path", - "is-glob", - "normalize-path", - "readdirp@3.6.0" - ], - "optionalDependencies": [ - "fsevents" - ] - }, "chokidar@5.0.0": { "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dependencies": [ - "readdirp@5.0.0" + "readdirp" ] }, "color-support@1.1.3": { @@ -1354,12 +1319,6 @@ "picomatch@4.0.4" ] }, - "fill-range@7.1.1": { - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": [ - "to-regex-range" - ] - }, "fsevents@2.3.3": { "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "os": ["darwin"], @@ -1384,37 +1343,20 @@ "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==", "bin": true }, - "glob-parent@5.1.2": { - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": [ - "is-glob" - ] - }, "iconv-lite@0.7.2": { "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dependencies": [ "safer-buffer" ] }, - "is-binary-path@2.1.0": { - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": [ - "binary-extensions" - ] + "image-size@2.0.2": { + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "bin": true }, "is-docker@3.0.0": { "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "bin": true }, - "is-extglob@2.1.1": { - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - }, - "is-glob@4.0.3": { - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": [ - "is-extglob" - ] - }, "is-in-ssh@1.0.0": { "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==" }, @@ -1425,9 +1367,6 @@ ], "bin": true }, - "is-number@7.0.0": { - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, "is-property@1.0.2": { "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, @@ -1510,9 +1449,6 @@ "node-releases@2.0.38": { "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==" }, - "normalize-path@3.0.0": { - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, "ohash@2.0.11": { "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==" }, @@ -1537,9 +1473,6 @@ "openapi-types@12.1.3": { "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" }, - "p-map@7.0.4": { - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==" - }, "partysocket@1.1.18": { "integrity": "sha512-SyuvH9VavWOSa14v6dYdp3yfSUDII4BQB1+TkGOFBkjfZKjnDBiba4fhdhwBlqGBkqw4ea3gTA1DYhSffX24Wg==", "dependencies": [ @@ -1599,12 +1532,6 @@ "destr" ] }, - "readdirp@3.6.0": { - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": [ - "picomatch@2.3.2" - ] - }, "readdirp@5.0.0": { "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==" }, @@ -1685,12 +1612,6 @@ "picomatch@4.0.4" ] }, - "to-regex-range@5.0.1": { - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": [ - "is-number" - ] - }, "type-fest@4.41.0": { "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" }, @@ -1713,16 +1634,6 @@ ], "bin": true }, - "vite-plugin-static-copy@4.1.0_vite@7.3.3": { - "integrity": "sha512-9XOarNV7LgP0KBB7AApxdgFikLXx3daZdqjC3AevYsL6MrUH62zphonLUs2a6LZc1HN1GY+vQdheZ8VVJb6dQQ==", - "dependencies": [ - "chokidar@3.6.0", - "p-map", - "picocolors", - "tinyglobby", - "vite" - ] - }, "vite@7.3.3": { "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dependencies": [ @@ -1780,13 +1691,12 @@ "npm:@badgateway/oauth2-client@^3.3.1", "npm:@hey-api/openapi-ts@0.97", "npm:@hey-api/vite-plugin@~0.3.1", - "npm:@imagemagick/magick-wasm@^0.0.40", "npm:@preact/signals@^2.9.0", "npm:@subframe7536/iconv-lite@~0.8.1", + "npm:image-size@^2.0.2", "npm:jose@^6.2.3", "npm:mysql2@^3.22.3", "npm:preact@^10.29.1", - "npm:vite-plugin-static-copy@^4.1.0", "npm:vite@^7.3.2" ] } diff --git a/src/lib/image.ts b/src/lib/image.ts index 80f3d5a..2199968 100644 --- a/src/lib/image.ts +++ b/src/lib/image.ts @@ -2,11 +2,7 @@ import type { AppBskyEmbedImages } from "@atcute/bluesky" import { type Did } from "@atcute/lexicons" import { isLegacyBlob } from "@atcute/lexicons/interfaces" -import { - ImageMagick, - initializeImageMagick, - MagickFormat, -} from "@imagemagick/magick-wasm" +import { imageSize } from "image-size" import { postFile } from "../traq/index.ts" import { client } from "./blueskyClient.ts" @@ -66,41 +62,36 @@ export const uploadImages = async ( return imageIds } -const isUint8ArrayOfArrayBuffer = ( - data: Uint8Array, -): data is Uint8Array => data.buffer instanceof ArrayBuffer - -const wasmUrl = import.meta.env.DEV - ? new URL( - "../../node_modules/@imagemagick/magick-wasm/dist/magick.wasm", - import.meta.url, - ) - : new URL("./magick.wasm", import.meta.url) -// `initializeImageMagick` accepts URL, but does not support protocols other than http(s). -// So we fetch the wasm file ourselves. -const initMagickPromise = fetch(wasmUrl).then((res) => res.arrayBuffer()) - .then((buf) => initializeImageMagick(buf)) - const resizeImage = async (imageBlob: Blob) => { - await initMagickPromise + const { height, width } = imageSize(await imageBlob.bytes()) + const imagePixels = height * width + + if (imagePixels <= TRAQ_IMAGE_MAX_PIXELS) { + return imageBlob + } + + const scale = Math.sqrt(TRAQ_IMAGE_MAX_PIXELS / imagePixels) + const resizeHeight = Math.max(1, Math.floor(height * scale)) + const resizeWidth = Math.max(1, Math.floor(width * scale)) + const resizedBitmap = await createImageBitmap(imageBlob, { + resizeHeight, + resizeQuality: "medium", + resizeWidth, + }) + const offscreen = new OffscreenCanvas(resizeWidth, resizeHeight) + const ctx = offscreen.getContext("bitmaprenderer") - return ImageMagick.read(await imageBlob.bytes(), (image) => { - const imagePixels = image.height * image.width + if (!ctx) { + throw new Error("Failed to get bitmaprenderer context") + } - if (imagePixels > TRAQ_IMAGE_MAX_PIXELS) { - const scale = Math.sqrt(TRAQ_IMAGE_MAX_PIXELS / imagePixels) - const newHeight = Math.max(1, Math.floor(image.height * scale)) - const newWidth = Math.max(1, Math.floor(image.width * scale)) + ctx.transferFromImageBitmap(resizedBitmap) - image.resize(newWidth, newHeight) - } + const blob = await offscreen.convertToBlob({ + type: "image/webp", + }) - return image.write(MagickFormat.WebP, (data) => { - if (isUint8ArrayOfArrayBuffer(data)) { - return new Blob([data], { type: "image/webp" }) - } + resizedBitmap.close() - throw new Error("Unexpected data type from ImageMagick") - }) - }) + return blob } diff --git a/vite-env.d.ts b/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/vite.config.ts b/vite.config.ts index e9cad24..f810959 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,6 @@ import { fresh } from "@fresh/plugin-vite" import { heyApiPlugin } from "@hey-api/vite-plugin" import { defineConfig } from "vite" -import { viteStaticCopy } from "vite-plugin-static-copy" export default defineConfig({ plugins: [ @@ -16,16 +15,6 @@ export default defineConfig({ output: "src/traq", }, }), - viteStaticCopy({ - environment: "ssr", - targets: [ - { - src: "node_modules/@imagemagick/magick-wasm/dist/magick.wasm", - dest: ".", - rename: { stripBase: true }, - }, - ], - }), ], resolve: { alias: {