D3.js pulling and embedding DataURI images with Promises - javascript

I'm building a data visualization which relies on a lot of small raster images, delivered as AWS URLs via JSON API.
This works fairly well, until I try to implement my next step, which is rendering the data visualization as a PNG to download. In the PNG, the raster images are broken.
I've understood that to solve this, I need to embed images as Data URLs.
Here's what I've got so far:
const companies_base64 = companies.map(c => {
var o = Object.assign({}, c)
o.base64 = imageToBase64(c.mimetype, c.logo)
return o
})
Where companies is an array of objects. Here's imageToBase64, the Heroku app being a clone of CORS anywhere:
function imageToBase64(mimetype, logo) {
var url = 'https://example.herokuapp.com/' + logo
return d3.blob(url)
.then(blob => blobToBase64(blob))
.then(base64 => mimetype + base64)
.catch(error => console.error(error))
}
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.onload = () => {
let dataUrl = reader.result
let base64 = dataUrl.split(',')[1]
resolve(base64)
}
reader.onerror = () => {
reject("Error")
}
reader.readAsDataURL(blob)
})
}
Which results in a Promise being returned when calling base64 on any of the objects in companies_base64, the [[PromiseValue]] being of course what I'm after. How am I supposed to make sure it is what gets returned so I can, ultimately, place it inside the xlink:href attributes of the <image>s in my <svg>?
I think that once it works and I can call imageToBase64 wherever, it's something I want to do only when the user presses Download. I imagine I can do this using D3, iterating over the <image>s and swapping out their xlink:href. Or should I go about it another way?
I have also tried getting the images as objects and then converting them to base64 in my RoR backend so they come packaged with the JSON, via an Image#to_base64 method. This does work, but it A) feels very wrong and B) is obviously very slow on initial load.
Thank you for your time and please bear with me as I am a beginner.

Your imageToBase64 function returns a promise, not the resolved data URL. That means you have to wait before you can attach them to the companies_base64 members. It is your choice if you do that as soon as the individual base64 string is ready, or if you wait for them all:
Promise.all(companies.map(c => {
return imageToBase64(c.mimetype, c.logo)
.then(u => Object.assign({ base64: u }, c))
.then(/* change the image reference here one by one... */)
}))
.then(companies_base64 => /* ...or here, in a loop over the array */)
.catch(error => console.error(error))

Related

How to send multiple images from FastAPI backend to JavaScript frontend using StreamingResponse?

I have a FastAPI endpoint /image/ocr that accepts, processes and returns multiple images as a StreamingResponse:
async def streamer(images):
for image in images:
# Bytes
image_bytes = await image.read()
# PIL PNG
image_data = Image.open(BytesIO(image_bytes))
# Process
# image_processed = ...
# Convert from PIL PNG to JPEG Bytes
image_ocr = BytesIO()
image_ocr.save(image_processed, format='JPEG')
yield image_ocr.getvalue()
#router.post('/ocr', summary='Process images')
async def ocr(images: List[UploadFile]):
return StreamingResponse(
streamer(images),
status_code=status.HTTP_200_OK,
media_type='image/jpeg'
)
In my React application I send multiple images to the /image/ocr endpoint using Axios configured to arraybuffer, and convert the returned images to Base64 using btoa, String.fromCharCode and Unit8Array:
const imageSubmit = async data => {
const formData = new FormData()
// A `useRef` that stores the uploaded images
data?.current?.files?.successful.map(image =>
formData.append('images', image.data)
)
try {
const response = await request.post(
'/image/ocr',
formData,
{responseType: 'arraybuffer'}
)
const base64String = btoa(
String.fromCharCode(
...new Uint8Array(response.data)
)
)
const contentType = response.headers['content-type']
const fullBase64String = `data:${contentType};base64,${base64String}`
return fullBase64String
} catch (error) {
// Error log
}
}
The problem is that I'm sending multiple images, for example, this image and this image, that are being accepted, processed and returned by the endpoint /image/ocr, but when I convert the response.data using the method in the last code snippet I only get one Base64 image.
I took a look at the raw response to see if I could iterate over the ArrayBuffer that I get through response.data:
Array Buffer object
But it didn't work with a for(const image of response.data) {}. I saw that ArrayBuffer has a slice method, but I'm not sure if it means that both images are in the ArrayBuffer and I need to slice and then convert, which is strange since at least one image is being converted to Base64 as of now, or convert it to another type since I saw that it can be done in some different ways here, here and here.
If the slice option is the right one I'm not sure how to select the start and end since the values in the ArrayBuffer are just random numbers and symbols.
Any idea on how to get both images in Base64 on my React application?
After hours of research I gave up on Axios since the documentation says that it "makes XMLHttpRequests from the browser", meaning that it doesn't support stream.
Reading more about the Streams API and watching a Google's series on Youtube, I started using Fetch API, as it supports streaming for both writing (16:26) and reading (6:24).
By replacing the try block in my question with this:
const response = await fetch('http://localhost:4000/image/ocr', {
method: 'POST',
headers: {
Authorization: ''
},
body: formData
})
const reader = response.body.getReader()
while (true) {
const { value, done } = await reader.read()
// Base64
const base64 = `data:${response.headers['content-type']};base64,${btoa(String.fromCharCode(...new Uint8Array(value)))}`
console.log(base64)
if (done) break
}
I'm able to use the asynchronous generator along with the StreamingResponse and get multiple images in my React application.
Its worth to dive deeper in the stream API since it can also work as a Websocket without having to write a specific back-end that deals with sockets

Is my function to convert image URLs to base64 returning corrupt data?

I'm using this script to convert image URLs to base64, but it's generating some funky strings that appear corrupt and I don't know why.
Regardless of if they are corrupt, Airtable is rejecting some of data strings that this function is generating and it's not easy to debug their error which is error: "INVALID_VALUE_FOR_COLUMN" message: "Field \"LogoBase64\" cannot accept the provided value"
The only thing Airtable should care about is if it's a primitive string or not.
Example incomeing URL: https://dl.airtable.com/.attachmentThumbnails/afffa0bf1eed11920c56956b71ee5195/5b979e22 but I don't think that's the problem. It just fails on certain images, when updating the db record, but some of base64 data returning from the function looks off to my uneducated eye.
I would love help strengthening this function to avoid generating corrupt data and in general, better handle thousands of images that might be JPG, PNG, HEIF, BMP and just skip incompatible formats.
async getBase64FromUrl(url) {
const data = await fetch(url);
const blob = await data.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
};
});
},
Example base64 output from one record below the function that seems corrupt to me, but maybe the endless AAAAAAAAAA's are whitespace?
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4RDuRXhpZgAATU0AKgAAAAgABAE7AAIAAAAMAAAISodpAAQAAAABAAAIVpydAAEAAAAYAAAQzuocAAcAAAgMAAAAPgAAAAAc6gAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENvbm9yIE1vcmFuAAAFkAMAAgAAABQAABCkkAQAAgAAABQAABC4kpEAAgAAAAM1NQAAkpIAAgAAAAM1NQAA6hwABwAACAwAAAiYAAAAABzqAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAxOTowNDozMCAxNzoyOTo1OAAyMDE5OjA0OjMwIDE3OjI5OjU4AAAAQwBvAG4AbwByACAATQBvAHIAYQBuAAAA/+ELHmh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSfvu78nIGlkPSdXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4NCjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iPjxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9InV1aWQ6ZmFmNWJkZDUtYmEzZC0xMWRhLWFkMzEtZDMzZDc1MTgyZjFiIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iLz48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+PHhtcDpDcmVhdGVEYXRlPjIwMTktMDQtMzBUMTc6Mjk6NTguNTUzPC94bXA6Q3JlYXRlRGF0ZT48L3JkZjpEZXNjcmlwdGlvbj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+PGRjOmNyZWF0b3I+PHJkZjpTZXEgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOmxpPkNvbm9yIE1vcmFuPC9yZGY6bGk+PC9yZGY6U2VxPg0KCQkJPC9kYzpjcmVhdG9yPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0n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```

how do you preload background img with react?

I have an APi where i pull data from, whereas the images come in the form of url,
i have made it so that when the fetch is done the app renders the feed.
The issue is that the images load after the fetch. How do i persist the loader until the images are done loading?
You didn't show us any code so we could help you much such as what libs you are using to fetch stuff from an api.
But here is one more generic way of doing it:
function loadEverything() {
// get the api response
const res = await fetch(url)
const json = await res.json()
// start loading all images
let images = json.images.map(image => new Promise((rs, rj) => {
const img = new Image()
img.onload = () => rs(img)
img.onerror = () => rj()
}))
// wait for everything to load
images = await Promise.all(images)
// now append everything to the DOM and remove the loader
}
means you have to append dom element and not simply bind some src attribute to a url
ofc, there is other ways to solve it too. like using a service worker + a custom cache storage. or hiding the DOM until everything is complete

How to load an HTML5 Canvas with an image from an image-service?

My web app calls a Web API service, which returns an image. The service returns nothing but an image. Calling the service is little different because there is a function in the routing code that adds the required auth-code and such. Anyway, my point is, I don't have the full URL and even if I did, I wouldn't want to pass it into code in plain-text. So what I have is a response, and that response is an image.
getThumb(filename: string) {
return this.http.get('/Picture/' + filename).subscribe(response => {
return response;
});
}
What I need to do is draw that image on to a canvas. From what I've seen on the internet so far, it looks like I want to create an image element, then assign that element src a URL, then I can add it to the canvas. It's the src part that's perplexing me. All the samples I see are either loading the image from a local filesystem or predefined URL, or from a base64 string, etc. I can't figure out how to just load an image I have as a response from a service. I'm sure I'm overthinking it.
Does anyone have some sample code to illustrate this?
e.g Something like this:
var img = new Image(); // Create new img element
img.src = ... ; // Set source to image
You could convert the image to Base64. In my example, you request the image and convert it to a blob using response.blob(). Once it's a blob, use fileReader.readAsDataURL to get the Base64.
const fileReader = new FileReader();
fetch("image-resource").then((response) => {
if(response.ok) {
return response.blob();
}
}).then((blob) => {
fileReader.readAsDataURL(blob);
fileReader.onloadend = () => {
console.log(fileReader.result);
}
});
References:
readAsDataURL
Blob

Getting number of audio channels for an AudioTrack

I have a video element, with data being added via MSE. I'm trying to determine how many audio channels there are in each track.
The AudioTrack objects themselves don't have a property with this information. The only way I know to go about it is to use the Web Audio API:
const v = document.querySelector('video');
const ctx = new OfflineAudioContext(32, 48000, 48000);
console.log(Array.from(v.audioTracks).map((track) => {
return ctx.createBufferSource(track.sourceBuffer).channelCount;
}));
For a video with a single mono track, I expect to get [1]. For a video with a single stereo track, I expect to get [2]. Yet, every time I get [2] no matter what the channel count is in the original source.
Questions:
Is there a proper direct way to get the number of channels in an AudioTrack?
Is there something else I could be doing with the Web Audio API to get the correct number of channels?
I stumbled upon an answer for this that seems to be working. It looks like by using decodeAudioData we can grab some buffer data about a file. I built a little function that returns a Promise with the buffer data that should return the correct number of channels of an audio file:
function loadBuffer(path) {
return fetch(path)
.then(response => response.arrayBuffer())
.then(
buffer =>
new Promise((resolve, reject) =>
audioContext.decodeAudioData(
buffer,
data => resolve(data),
err => reject(err)
)
)
)
}
Then you can use it like this:
loadBuffer(audioSource).then(data => console.log(data.numberOfChannels))
Might be best to store and reuse the data if it can be called multiple times.

Categories