javascriptnode.jstypescriptdenowhatwg-streams-api

cast/convert ReadableStream<Uint8Array> to Uint8ClampedArray


I have a ReadableStream<Uint8Array> and need to convert it to Uint8ClampedArray in TypeScript Deno.

I tried some solutions I could find. Like this:

const getImageData = image => {
  const canvas = document.createElement("canvas");
  canvas.width = image.width;
  canvas.height = image.height;
  const context = canvas.getContext("2d");
  if (context != null) {
    context.drawImage(image, 0, 0);
    return context.getImageData(0, 0, image.width, image.height);
  }
};

But it already fails because document can't be found.

Is there a straight forward casting/converting from ReadableStream<Uint8Array> to Uint8ClampedArray?

My ReadableStream comes from this:

const image = (await fetch(url)).body!;

I want to do all this, to create a blurhash. But when just using the Uint8Array I get ValidationError: Width and height must match the pixels array


Solution

  • If you have a ReadableStream and don't know the byte length of the stream, then you can collect the bytes and concatenate them in order to construct an instance of Uint8ClampedArray:

    Code in TypeScript Playground

    /** Consumes the stream */
    async function intoUint8ClampedArray(
      readable: ReadableStream<Uint8Array>,
    ): Promise<Uint8ClampedArray> {
      const chunks: Uint8Array[] = [];
      let cursor = 0;
    
      await readable.pipeTo(
        new WritableStream({
          write(chunk) {
            cursor += chunk.byteLength;
            chunks.push(chunk);
          },
        }),
      );
    
      const clamped = new Uint8ClampedArray(cursor);
      cursor = 0;
    
      for (const chunk of chunks) {
        clamped.set(chunk, cursor);
        cursor += chunk.byteLength;
      }
    
      return clamped;
    }
    

    Use it with your example code like this:

    const response = await fetch("https://example.com/");
    
    if (!response.body) {
      // Handle case where body is null here…
      throw new Error("Body null");
    }
    
    const clamped = await intoUint8ClampedArray(response.body);
    
    // Use the Uint8ClampedArray…
    

    However, if you're starting with an instance of Response then you can use its async method arrayBuffer() to construct the clamped array more simply:

    const response = await fetch("https://example.com/");
    const clamped = new Uint8ClampedArray(await response.arrayBuffer());
    // Use the Uint8ClampedArray…