Coder Social home page Coder Social logo

monogrid / gainmap-js Goto Github PK

View Code? Open in Web Editor NEW
75.0 6.0 5.0 70.5 MB

A Javascript (TypeScript) Port of Adobe Gainmap Technology for storing HDR Images using an SDR Image + a gainmap

Home Page: https://monogrid.github.io/gainmap-js/

License: MIT License

JavaScript 6.43% TypeScript 92.72% HTML 0.85%
hdr hdr-image three threejs adobe-gainmap gainmap ultrahdr

gainmap-js's Introduction

gainmap-js

FOSSA Status

A Javascript (TypeScript) Encoder/Decoder Implementation of Adobe's Gain Map Technology for storing HDR Images using an SDR Image + a "Gain map"

⚠️ This library is primarily intended for encoding and decoding gain map images for the three.js 3D Library

It can be used for general encode/decode of gain maps but it depends on the three.js library which, in itself, is quite heavy if you only use it to encode/decode gain maps.

Live Demo

https://monogrid.github.io/gainmap-js/

Compares loading:

  1. a JPEG file with embedded gain map data
  2. a webp sdr file + a webp gain map + metadata JSON
  3. a comparable size .hdr file for comparison

Use it to convert .hdr and .exr files into gain maps. It's free and the whole process happens in your browser.

Installing

$ npm install @monogrid/gainmap-js three

What is a Gain map?

See here for a detailed explanation, here are some relevant parts:

A gain map is a single file with a second pseudo-image embedded in it to create an optimized result for a specific monitor. It can be used to generate the HDR version (which looks dramatically better where supported), the SDR version (without tone mapping to ensures great quality), or anything in between (to better support less capable HDR displays).

Gain maps are not a new type of file, but rather a technology which can be embedded into a variety of image formats. There are reference specs already for the JPG, AVIF, JXL, and HEIF file formats. JPG is especially notable as it could not properly support HDR without gain maps and it offers a very useful bridge to the future (i.e. highly compatible with today’s software).

A gain map includes:

  • A base (default) image. This can be an SDR or an HDR image (JPG gain maps are always encoded with SDR as the base). If the browser or viewing software does not understand gain maps, it will just the treat file as if it were just the base image.
  • The gain map. This is a secondary “image” embedded in the file. It is not a real image, but rather contains data to convert each pixel from the base image into the other (SDR or HDR) version of the image.
  • Gain map metadata. This tells the browser how the gain map is encoded as well as critical information to optimize rendering on any display.

Please note that Google is adopting the gain map technology in Android 14 but its naming of the technology refers to it as Ultra HDR Image Format and a JPEG file with embedded gain map is apparently called JPEGR in their terminology, we call it HDRJPEG for the moment.

API

Refer to the WIKI for detailed documentation about the API.

Examples

Decoding

The main use case of this library is to decode a JPEG file that contains gain map data and use it instead of a traditional .exr or .hdr image.

Using a single JPEG with embedded Gain map Metadata

This approach lets you load a single file with an embedded Gain Map.

The advantage is to have a single file to load.

The disadvantages are:

  • No WEBP compression
  • The JPEG cannot be manipulated in Photoshop, GIMP, or any other software that does not support the gain map technology (no photo editing software supports it at the time of writing 06-11-2023).
  • Photo sharing websites and/or services (i.e. sharing with Slack) will likely strip the Gain map metadata and the HDR information will be lost, leaving you with only the SDR Representation.
import { HDRJPGLoader } from '@monogrid/gainmap-js'
import {
  EquirectangularReflectionMapping,
  Mesh,
  MeshBasicMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene,
  WebGLRenderer
} from 'three'

const renderer = new WebGLRenderer()

const loader = new HDRJPGLoader(renderer)

const result = await loader.loadAsync('gainmap.jpeg')
// `result` can be used to populate a Texture

const scene = new Scene()
const mesh = new Mesh(
  new PlaneGeometry(),
  new MeshBasicMaterial({ map: result.renderTarget.texture })
)
scene.add(mesh)
renderer.render(scene, new PerspectiveCamera())

// Starting from three.js r159
// `result.renderTarget.texture` can
// also be used as Equirectangular scene background
//
// it was previously needed to convert it
// to a DataTexture with `result.toDataTexture()`
scene.background = result.renderTarget.texture
scene.background.mapping = EquirectangularReflectionMapping

// result must be manually disposed
// when you are done using it
result.dispose()

Using separate files

Using separate files will get rid of the limitations of using a single JPEG file but it will force to use three separate files

  1. An SDR Representation file
  2. A Gainmap file
  3. A JSON containing the gainmap metadata used for decoding
import { GainMapLoader } from '@monogrid/gainmap-js'
import {
  EquirectangularReflectionMapping,
  Mesh,
  MeshBasicMaterial,
  PerspectiveCamera,
  PlaneGeometry,
  Scene,
  WebGLRenderer
} from 'three'

const renderer = new WebGLRenderer()

const loader = new GainMapLoader(renderer)

const result = await loader.loadAsync(['sdr.jpeg', 'gainmap.jpeg', 'metadata.json'])
// `result` can be used to populate a Texture

const scene = new Scene()
const mesh = new Mesh(
  new PlaneGeometry(),
  new MeshBasicMaterial({ map: result.renderTarget.texture })
)
scene.add(mesh)
renderer.render(scene, new PerspectiveCamera())

// Starting from three.js r159
// `result.renderTarget.texture` can
// also be used as Equirectangular scene background
//
// it was previously needed to convert it
// to a DataTexture with `result.toDataTexture()`
scene.background = result.renderTarget.texture
scene.background.mapping = EquirectangularReflectionMapping

// result must be manually disposed
// when you are done using it
result.dispose()

Encoding

Encoding a Gain map starting from an EXR file.

This is generally not useful in a three.js site but this library exposes methods that allow to encode an .exr or hdr file into a jpeg with an embedded gain map.

import { compress, encode, findTextureMinMax } from '@monogrid/gainmap-js/encode'
import { encodeJPEGMetadata } from '@monogrid/gainmap-js/libultrahdr'
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js'

// load an HDR file
const loader = new EXRLoader()
const image = await loader.loadAsync('image.exr')

// find RAW RGB Max value of a texture
const textureMax = findTextureMinMax(image)

// Encode the gainmap
const encodingResult = encode({
  image,
  // this will encode the full HDR range
  maxContentBoost: Math.max.apply(this, textureMax)
})

// obtain the RAW RGBA SDR buffer and create an ImageData
const sdrImageData = new ImageData(encodingResult.sdr.toArray(), encodingResult.sdr.width, encodingResult.sdr.height)
// obtain the RAW RGBA Gain map buffer and create an ImageData
const gainMapImageData = new ImageData(encodingResult.gainMap.toArray(), encodingResult.gainMap.width, encodingResult.gainMap.height)

// parallel compress the RAW buffers into the specified mimeType
const mimeType = 'image/jpeg'
const quality = 0.9

const [sdr, gainMap] = await Promise.all([
  compress({
    source: sdrImageData,
    mimeType,
    quality,
    flipY: true // output needs to be flipped
  }),
  compress({
    source: gainMapImageData,
    mimeType,
    quality,
    flipY: true // output needs to be flipped
  })
])

// obtain the metadata which will be embedded into
// and XMP tag inside the final JPEG file
const metadata = encodingResult.getMetadata()

// embed the compressed images + metadata into a single
// JPEG file
const jpeg = await encodeJPEGMetadata({
  ...encodingResult,
  ...metadata,
  sdr,
  gainMap
})

// `jpeg` will be an `Uint8Array` which can be saved somewhere

// encoder must be manually disposed
// when no longer needed
encodingResult.gainMap.dispose()
encodingResult.sdr.dispose()

Libultrahdr in Vite

If you import @monogrid/gainmap-js/libultrahdr You will need to exclude it from Vite optimizations.

// vite.config.js

module.exports = defineConfig({
  ...
  optimizeDeps: {
    exclude: ['@monogrid/gainmap-js/libultrahdr']
  },
  ...
})

Building with full encoding support (libultrahdr-wasm)

Clone the repository with git submodules recursively:

$ git clone --recurse-submodules [email protected]:MONOGRID/gainmap-js.git

Proceed to build the libultrahdr-wasm module following the documentation found here, here's a quick summary

$ cd gainmap-js/libultrahdr-wasm/

Create a meson "cross compile config" named em.txt and place the following content inside:

[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
nm = 'emnm'

[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'

Then execute

$ meson setup build --cross-file=em.txt
$ meson compile -C build

After compiling the WASM, head back to the main repository

$ cd ..
$ npm i
$ npm run build

Building with no encoding support (requires no wasm)

⚠️ Building the library with decode only capabilities will not allow to run playwright e2e tests with npm run test this method should only be used by people who would like to customize the "decoding" part of the library but are unable to build the WASM module for some reason (emscripten can be tricky sometimes, I've been there)

Clone the repository normally:

$ git clone [email protected]:MONOGRID/gainmap-js.git
$ cd gainmap-js
$ npm i

build with

$ npm run build --config rollup.config.decodeonly.mjs

References

License

FOSSA Status

gainmap-js's People

Contributors

daniele-pelagatti avatar dependabot[bot] avatar drcmda avatar fossabot avatar semantic-release-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

gainmap-js's Issues

Put decoder library on npm or CDN link for easy import

Request to put the decoder library on npm so that threejs projects can import from a CDN link to make loading of gainmaps easier. Also this will make it easier to update projects with the latest version of the gainmap library.

Support for WebGPU Renderer

In r167 we now use import from 'three/webgpu'

It breaks @monogrid/gainmap-js with the error : WARNING: Multiple instances of Three.js being imported.

What will be the best workaround ?

.setRenderer(gl) on the LoaderBase class

Would it be possible to make the gl inject optional + an additional .setRenderer(gl) method on the loader (the LoaderBase class to be exact)? so that the end user can do:

const loader = new HDRJPGLoader()
loader.setRenderer(gl)

the THREE.Loader interface doesn't have a common interface because three lacks types, but it can generally be assumed that it is no parameters on the constructor + set functions, that's how most (all?) loaders worked. for instance:

const gltf = new GLTFLoader()
gltf.setDRACOLoader(new DRACOLoader())

this becomes important once you have loader managing layers that rely on uniform structure. one such example is react-three-fibers useLoader, but tresjs, threlte etc will run into the same problems.

current workaround: https://codesandbox.io/p/sandbox/sleepy-jackson-hqct6n?file=%2Fsrc%2FApp.js%3A35%2C3-35%2C101

const quad = useLoader(HDRJPGLoader, 'spruit_sunrise_4k.jpg', (loader) => (loader._renderer = gl))

we would like to add a shortcut/helper to drei but we can't rely on internals for that, it would be too risky. if we can rely on the api users would then be able to just do

<Environment files="spruit_sunrise_4k.jpg" />

Docs for encoding seem out of date?

Hello! I'm trying to encode a three.js RGBA float texture to a gain map and using the suggested snippet from the Encoding section to try it. However I'm getting the following errors from parcel when building:

@parcel/core: node_modules/@monogrid/gainmap-js/dist/decode.js does not export 'compress'

  /Users/garrett/Desktop/git/three-gpu-pathtracer/example/hdr.js:17:10
    16 | import { LoaderElement } from './utils/LoaderElement.js';
  > 17 | import { compress, encode, findTextureMinMax } from '@monogrid/gainmap-js';
  >    |          ^^^^^^^^
    18 | import { encodeJPEGMetadata } from '@monogrid/gainmap-js/dist/libultrahdr.js';
    19 | import { WebGLPathTracer } from '..';

@parcel/core: node_modules/@monogrid/gainmap-js/dist/decode.js does not export 'encode'

  /Users/garrett/Desktop/git/three-gpu-pathtracer/example/hdr.js:17:20
    16 | import { LoaderElement } from './utils/LoaderElement.js';
  > 17 | import { compress, encode, findTextureMinMax } from '@monogrid/gainmap-js';
  >    |                    ^^^^^^
    18 | import { encodeJPEGMetadata } from '@monogrid/gainmap-js/dist/libultrahdr.js';
    19 | import { WebGLPathTracer } from '..';

@parcel/core: node_modules/@monogrid/gainmap-js/dist/decode.js does not export 'findTextureMinMax'

  /Users/garrett/Desktop/git/three-gpu-pathtracer/example/hdr.js:17:28
    16 | import { LoaderElement } from './utils/LoaderElement.js';
  > 17 | import { compress, encode, findTextureMinMax } from '@monogrid/gainmap-js';
  >    |                            ^^^^^^^^^^^^^^^^^
    18 | import { encodeJPEGMetadata } from '@monogrid/gainmap-js/dist/libultrahdr.js';
    19 | import { WebGLPathTracer } from '..';

Specifically the compress, encode, and findTextureMinMax functions to not seem to be exported from thethe main "gainmap-js" export (which maps to the dist/decode.js file). In fact I don't see any of those exports anywhere in the node modules distribution outside of comments. Is there a new recommended way to encode a FloatType RGBAFormat render target to a gain map?

Thanks!

No “exports” main defined in @monogrid/gainmap-js

now that monogrid is in drei users experience issues with bundling environments like next-js.

https://discourse.threejs.org/t/trying-to-use-react-three-drei-in-next-js-error-no-exports-main-defined-in-monogrid-gainmap-js-package-json/63586

Server Error
Error: No “exports” main defined in /Users/primozrome/Documents/WEB/dewesoft-website/node_modules/@monogrid/gainmap-js/package.json
This error happened while generating the page. Any console logs will be displayed in the terminal window.

Confusion about gainmap-js

Hello, I have used gainmap-js.
But the effect is not ideal.
This is a comparison chart.
As you can see Using HDR_JPG to apply background, the image displays jagged.
But using HDR is relatively smooth.
74e1ca6153ddfef40c510d4eed9155a72359fbbc_2_1035x507

ecc0ab8e45e4f16cc5558352034d998f53840338_2_1035x505

My steps are:
1、Download a 4K HDR file (sunflowers_puresky_4k. hdr)
2、Use tool convert it to HDR_JPG (sunflowers_puresky_4k. jpg)
3、Refer to the official code of gainmap-js

This is my code:

<template>
    <div>
    </div>
</template>

<script>

    import * as THREE from 'three';
    import {GUI} from 'three/examples/jsm/libs/lil-gui.module.min.js';
    import Stats from 'three/examples/jsm/libs/stats.module.js';
    import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
    import {RGBELoader} from 'three/examples/jsm/loaders/RGBELoader.js';
    import {GainMapLoader, HDRJPGLoader} from '@monogrid/gainmap-js';

    const params = {
        envMap: 'HDR JPG',
        roughness: 0.0,
        metalness: 1.0,
        exposure: 1.0
    };

    let container, stats;
    let camera, scene, renderer, controls;
    let torusMesh;
    let hdrJpg, hdrJpgPMREMRenderTarget, hdrJpgEquirectangularMap;
    let gainMap, gainMapPMREMRenderTarget, gainMapBackground;
    let hdrPMREMRenderTarget, hdrEquirectangularMap;

    export default {
        mounted() {
            this.init();

            this.animate();

            // 监听窗口变化
            window.addEventListener("resize", () => {
                // 重置渲染器宽高比
                renderer.setSize(window.innerWidth, window.innerHeight);
                // 重置相机宽高比
                camera.aspect = window.innerWidth / window.innerHeight;
                // 更新相机投影矩阵
                camera.updateProjectionMatrix();
            });
        },
        methods: {
            animate() {
                requestAnimationFrame(this.animate);
                stats.begin();
                this.render();
                stats.end();
            },
            render() {

                let pmremRenderTarget, equirectangularMap;

                switch (params.envMap) {
                    case 'HDR JPG':
                        pmremRenderTarget = hdrJpgPMREMRenderTarget;
                        equirectangularMap = hdrJpgEquirectangularMap;
                        break;
                    case 'Webp Gain map (separate)':
                        pmremRenderTarget = gainMapPMREMRenderTarget;
                        equirectangularMap = gainMapBackground;
                        break;
                    case 'HDR':
                        pmremRenderTarget = hdrPMREMRenderTarget;
                        equirectangularMap = hdrEquirectangularMap;
                        break;
                }

                torusMesh.material.roughness = params.roughness;
                torusMesh.material.metalness = params.metalness;

                scene.environment = equirectangularMap;
                scene.background = equirectangularMap;
                renderer.toneMappingExposure = params.exposure;

                renderer.render(scene, camera);
            },
            init() {
                //
                container = document.createElement('div');
                document.body.appendChild(container);

                //
                camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 500);
                camera.position.set(0, 0, -120);

                //
                scene = new THREE.Scene();

                //
                renderer = new THREE.WebGLRenderer();
                renderer.toneMapping = THREE.ACESFilmicToneMapping;
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(window.innerWidth, window.innerHeight);
                container.appendChild(renderer.domElement);

                // let geometry = new THREE.TorusKnotGeometry(18, 8, 200, 40, 1, 3);
                let geometry = new THREE.BoxGeometry(10, 10, 10);

                let material = new THREE.MeshStandardMaterial({
                    color: 0xffffff,
                    metalness: params.metalness,
                    roughness: params.roughness
                });
                torusMesh = new THREE.Mesh(geometry, material);
                scene.add(torusMesh);
                geometry = new THREE.PlaneGeometry(200, 200);
                material = new THREE.MeshBasicMaterial();
                const pmremGenerator = new THREE.PMREMGenerator(renderer);
                pmremGenerator.compileEquirectangularShader();

                THREE.DefaultLoadingManager.onLoad = function () {
                    pmremGenerator.dispose();
                };

                hdrJpg = new HDRJPGLoader(renderer)
                    .load('textures/sunflowers_puresky_4k.jpg', function () {
                        hdrJpgEquirectangularMap = hdrJpg.renderTarget.texture;
                        hdrJpgPMREMRenderTarget = pmremGenerator.fromEquirectangular(hdrJpgEquirectangularMap);
                        hdrJpgEquirectangularMap.mapping = THREE.EquirectangularReflectionMapping;
                        hdrJpgEquirectangularMap.needsUpdate = true;
                        hdrJpg.dispose();

                    }, function (progress) {
                        console.log('jpg', progress);
                    });

                gainMap = new GainMapLoader(renderer)
                    .load([
                        'textures/gainmap/spruit_sunrise_4k.webp',
                        'textures/gainmap/spruit_sunrise_4k-gainmap.webp',
                        'textures/gainmap/spruit_sunrise_4k.json'
                    ], function () {
                        gainMapBackground = hdrJpg.renderTarget.texture;
                        gainMapPMREMRenderTarget = pmremGenerator.fromEquirectangular(gainMapBackground);
                        gainMapBackground.mapping = THREE.EquirectangularReflectionMapping;
                        gainMapBackground.needsUpdate = true;
                        gainMap.dispose();
                    }, function (progress) {
                    });

                hdrEquirectangularMap = new RGBELoader()
                    .load('textures/sunflowers_puresky_4k.hdr', function () {
                        hdrPMREMRenderTarget = pmremGenerator.fromEquirectangular(hdrEquirectangularMap);
                        hdrEquirectangularMap.mapping = THREE.EquirectangularReflectionMapping;
                        hdrEquirectangularMap.minFilter = THREE.LinearFilter;
                        hdrEquirectangularMap.magFilter = THREE.LinearFilter;
                        hdrEquirectangularMap.needsUpdate = true;
                    }, function (progress) {
                    });

                //
                controls = new OrbitControls(camera, renderer.domElement);
                controls.minDistance = 50;
                controls.maxDistance = 300;

                //
                stats = new Stats();
                container.appendChild(stats.dom);

                //
                const gui = new GUI();
                gui.add(params, 'envMap', ['HDR JPG', 'Webp Gain map (separate)', 'HDR']).onChange();
                gui.add(params, 'roughness', 0, 1, 0.01);
                gui.add(params, 'metalness', 0, 1, 0.01);
                gui.add(params, 'exposure', 0, 2, 0.01);
                gui.open();
            },
        },
    };
</script>

Excuse me, is there an error in any step?

Can't build the project

Hi,

I'm getting this error after cloning this repo, running npm install and running npm run prepack.

This is the error:

import { MainModule } from '../../libultrahdr-wasm/build/libultrahdr'

I found this repo but I can't understand what should I copy from there to libultrahdr-wasm/build/.

encodeJPEGMetadata failed: Object in bad state, mem alloc failed

image
.hdr -> jpg with gainmap
80% of my .hdr file failed with this error, only few works

And for Adobe gainmap sample jpgs (from https://helpx.adobe.com/camera-raw/using/gain-map.html) , or gainmap jpgs exported by Adobe Camera Raw
I tried to repack or re-encode them (because Galaxy S24 Ultra's Album / Instagram cannot display them in HDR, but Google Album or Chrome can):
extractGainmapFromJPEG -> encodeJPEGMetadata
or
extractGainmapFromJPEG -> compress both sdr and gainmap -> encodeJPEGMetadata
all failed with "Object in bad state, mem alloc failed"

I suspect the issue might be related to the libultrahdr

peerDependencies < 0.159.0

Hey,

is there any known problem with the new released threejs version? or can this be updated to < 0.160.0 three version?

sdrJPEG from Loader for custom shader skybox

Hi,

i trying to add this fantastic gainmaps to a custom skyshader ( sdJPEG) https://github.com/mrxz/fern-aframe-components/blob/main/sky-background/src/index.js

the problem is the generated sd Texture, has generated mipmaps and there are visual Seams

what i doing now is:

 const result = await hdrjpgLoader.loadAsync(this.data.textureSrc);
 let sd = result._quad.material.sdr;
 sd.generateMipmaps = false;
 sd.needsUpdate = true;
 let texture = result.renderTarget.texture;
 result.dispose();

using the sd for the custom shader map value and the texture for the Scene environment, this works but not sure if there is a better way? ( or are the mipmaps needed on the sd Texture in the gainmap Material ? )

Decoder hang with invalid URL

We have a test that's now failing upon incorporating HDRJPGLoader in google/model-viewer#4578:

test('throws on invalid URL', async () => {
      try {
        await textureUtils.loadEquirect('');
        expect(false).to.be.ok;
      } catch (e) {
        expect(true).to.be.ok;
      }
    });

It appears to time out reliably, so I think it's just hanging instead of throwing? Our similar test for a not found URL is throwing properly: await textureUtils.loadEquirect('./nope.png');

Kudos to you - Readme suggestion

Not really an issue, just wanted to say thanks for the library, i recently tried with three as envMap lighting and i was able to save like 2mb in filesize.
Although I kinda suggest to maybe create and explicit a note on the fact you still need to use PMREMGenerator if you want to use that as environment, otherwise it won't work. I took me a while to realise that, was using directly as envmap eheh

  const pmremGenerator = new PMREMGenerator(gl)
  pmremGenerator.compileEquirectangularShader()

  const loader = new HDRJPGLoader(gl)
  loader.loadAsync('..../lighting.jpg').then(res => {
    const envMap = pmremGenerator.fromEquirectangular(res.renderTarget.texture).texture
    scene.value.environment = envMap
  })

Anyway, thanks again ❤️

HDRJPGs with resolutions higher than 4K crash on mobile (iOS iPhone 15)

Using a HDRJPG with higher resolutions than 4096 in width seem to crash mobile webpage. Not sure if this is due to iOS and some Safari artificial VRAM limitations or something in the codebase but my guess is iOS. Above 4K HDRJPGs seem to work fine on iPad Pro M2 fine, and desktops as well. Gotten up to 6K on iPad Pro and 8K on desktop.

Curious to hear if anyone else has encountered this problem.

ios 14 webgl1

Hey,

i have done some testing on Browserstack and ios and see a problem on ios 14 devices ( wegl1)

Unhandled Promise Rejection: TypeError: images[0].arrayBuffer is not a function. (In 'images[0].arrayBuffer()', 'images[0].arrayBuffer' is undefined)

Bildschirmfoto vom 2023-12-04 18-14-39

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.