How to get img dimmensions using rescript

Hello:

I have an image uploaded from a file input

photo: Webapi.Blob.t

I have managed to create the URL and put into an image tag

let url = Url.createBlobUrl
let image = <img src=url />

But I have no clue on how to get now image dimmensions, can anyone help.

Thanks in advance
Cheers

UPDATE

I did this:

let processImage = %raw(`
  (url) => {
    return new Promise((resolve, reject) => {
      const img = document.createElement("img");
      img.src = url;
      img.onload = resolve;
      img.onerror = reject;
      document.body.appendChild(img);
      document.body.removeChild(img);
    });
  }
`)

And then

    url->processImage
    ->thenResolve((event: Webapi.Dom.Event.t) => {
      ignore(event)
      let width = %raw(`event.currentTarget.naturalWidth`)
      let height = %raw(`event.currentTarget.naturalHeight`)
     ...

Is there a way to do this whithout the raw sentence ??

Using webapi is a bit like writing DOM code with TypeScript strict enabled. If the spec says something can be any element, that’s the type you get. If the spec says something might be null, it will return an optional value. Code can’t assume document.createElement and event.currentTarget return the element you know they do; the value needs to be converted (or coerced) into image elements.

The library also doesn’t map onload or onerror, the more modern event listener style is all it makes available.

So, the first block:

open Webapi.Dom;

let processImage = url => {
  Promise.make((resolve, reject) => {
    // showing each step of the chain, this can be done more easily (see below)
    let e = document->Document.createElement("img")
    let maybeImg = HtmlImageElement.ofElement(e)
    let img = Belt.Option.getUnsafe(maybeImg)

    img->HtmlImageElement.setSrc(url)
    img->HtmlImageElement.addLoadEventListener(event => resolve(. event))
    img->HtmlImageElement.addErrorEventListener(error => reject(. error))

    let body =
      document
      ->Document.asHtmlDocument
      ->Belt.Option.flatMap(HtmlDocument.body)
      ->Belt.Option.getUnsafe

    body->Element.appendChild(~child=img->HtmlImageElement.asNode)
    body->Element.removeChild(~child=img->HtmlImageElement.asNode)->ignore
  })
}

Note that Belt.Option.getUnsafe is not a great thing to have in production code but given the element was only just created, it’s fine (as the JS is).

The second becomes:

url
->processImage
->Promise.thenResolve(event => {
  // similar to the above chain, but done all at once without interim variables
  let img =
    event
    ->Event.currentTarget
    ->EventTarget.unsafeAsElement
    ->HtmlImageElement.ofElement
    ->Belt.Option.getUnsafe
  let width = img->HtmlImageElement.naturalWidth
  let height = img->HtmlImageElement.naturalHeight
  // ...
})

I normally wouldn’t use getUnsafe here, preferring a switch statement to unwrap the optional image properly, but it’ll do for this example.

1 Like

Since I had already written this up for you I published it as an example in webapi. The generated JS shows, if you gloss over the runtime curry and belt management code, something not far from the original JS.

4 Likes

My friend get to this other solution which I think is cleaner


let makeHtmlImageElementFromUrl = (url: string) => {
  Promise.make((~resolve, ~reject) => {
    let img = Webapi.Dom.HtmlImageElement.make()
    img->Webapi.Dom.HtmlImageElement.addLoadEventListener(_event => {resolve(. img)}, _)
    img->Webapi.Dom.HtmlImageElement.addErrorEventListener(
      _error => {reject(. Js.Exn.raiseError("Faild to load the image"))},
      _,
    )

    img->Webapi.Dom.HtmlImageElement.setSrc(url)
  })
}

let isLandscape = (photo: Webapi.Blob.t) => {
  photo
  ->LocalFile.Url.createBlobUrl
  ->makeHtmlImageElementFromUrl
  ->then(img => {
    let width = img->Webapi.Dom.HtmlImageElement.width
    let height = img->Webapi.Dom.HtmlImageElement.height
    resolve(Ok(width > height))
  })
  ->catch(error => {
    resolve(Error(error))
  })
}


Thanks a lot for your response!!