evanw / thumbhash

A very compact representation of an image placeholder
https://evanw.github.io/thumbhash/
MIT License
3.57k stars 71 forks source link

Deno support? #35

Open kierancrown opened 6 months ago

kierancrown commented 6 months ago

Does anybody have experience getting this to run in Deno?

My output is constantly looking like this

Uint8Array(24) [
  0, 0, 0, 7, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0
]

While my input looks like this

ArrayBuffer {
  [Uint8Contents]: <ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 48 00 48 00 00 ff e1 00 8c 45 78 69 66 00 00 4d 4d 00 2a 00 00 00 08 00 05 01 12 00 03 00 00 00 01 00 01 00 00 01 1a 00 05 00 00 00 01 00 00 00 4a 01 1b 00 05 00 00 00 01 00 00 00 52 01 28 00 03 00 00 00 01 00 02 00 00 87 69 00 04 00 00 00 01 00 00 00 5a ... 3143 more bytes>,
  byteLength: 3243
}

I've been trying to get this to work for a few days now. Wondering how this works in Deno as it works perfectly fine in NodeJS. Maybe due to the Buffer object

evanw commented 6 months ago

Some thoughts:

kierancrown commented 6 months ago

Some thoughts:

  • Do you have some code to reproduce your issue?
  • Are you calling rgbaToThumbHash in Deno? That expects decoded RGBA data as input, but the input you posted here looks like a JPEG file instead of RGBA (byteLength is not a multiple of 4 and the data starts with ff d8). You're supposed to decode the image first.

Sure this is the example I've managed to cobble together so far based on the NodeJS example. Do you know of a way to convert into the expected format? Sharp isn't available for Deno. I'm using ImageMagick but it obviously isn't exporting in the same way

// Setup type definitions for built-in Supabase Runtime APIs
/// <reference types="https://esm.sh/v135/@supabase/functions-js@2.3.1/src/edge-runtime.d.ts" />

import { createClient } from "https://esm.sh/@supabase/supabase-js@2.7.1";
import {
  ImageMagick,
  IMagickImage,
  initialize,
  MagickFormat,
} from "https://deno.land/x/imagemagick_deno/mod.ts";
import { Buffer } from "jsr:@std/io/buffer";
import * as ThumbHash from "npm:thumbhash@0.1.1";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey",
};

interface WebhookPayload {
  type: "INSERT";
  table: string;
  record: any;
  schema: "public";
}

const supabase = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);

const resizeImage = (image: Uint8Array): Promise<Uint8Array> => {
  return new Promise((resolve) => {
    (async () => {
      // Initialize ImageMagick
      await initialize();
      await ImageMagick.read(image, async (img: IMagickImage) => {
        img.resize(100, 100);

        await img.write(MagickFormat.Jpeg, (data: Uint8Array) => {
          resolve(data);
        });
      });
    })();
  });
};

Deno.serve(async (req) => {
  const payload: WebhookPayload = await req.json();
  console.log("Request received", JSON.stringify(payload, null, 2));

  try {
    // Get the trigger data which contains the path of the image
    const triggerData = payload.record;

    // Download the image from the bucket based on the provided path
    const { data: imageData, error: imageError } = await supabase.storage
      .from("user_profile_pictures")
      .download(triggerData.path);
    if (imageError) throw imageError;

    console.log((await imageData.arrayBuffer()).byteLength);

    // Convert image data to buffer
    const imageBuffer = await imageData.arrayBuffer();
    const input = new Uint8Array(imageBuffer, 0, imageBuffer.byteLength);
    const resizedImageBuffer = await resizeImage(input);

    const binaryThumbHash = ThumbHash.rgbaToThumbHash(
      100,
      100,
      resizedImageBuffer
    );

    console.log("binaryThumbHash:", new Buffer(binaryThumbHash));

    // Update the database with the generated thumbhash
    const { error: updateError } = await supabase
      .from(payload.table)
      .update({ thumbhash: binaryThumbHash })
      .eq("path", triggerData.path);
    if (updateError) throw updateError;

    return new Response(JSON.stringify({ success: true }), {
      headers: { ...corsHeaders, "Content-Type": "application/json" },
      status: 200,
    });
  } catch (error) {
    console.error(error);

    return new Response(JSON.stringify({ error: error.message }), {
      headers: { ...corsHeaders, "Content-Type": "application/json" },
      status: 400,
    });
  }
});