简体   繁体   中英

Convert binary data to base64 does not work with btoa unescape

I am new to React and want to display an image downloaded as binary data. I download the image data from api call to adobe lightroom api. The api call works since the image is displayed in Postman without problems. I can also save the image data to a jpeg-file and it is displayed ok.

In React I want to do <img src={`data:image/jpeg;base64,${theImage}`} /> and for that to work I need to convert the binary data to a base64 encoded string. When i convert the downloaded jpeg using cat image.jpeg|base64 > base64.txt the resulting string works in my React app.

But when I try var theImage = btoa(binarydata) in React I get Unhandled Rejection (InvalidCharacterError): Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

After searching the issue I try use var theImage = btoa(unescape(encodeURIComponent( binarydata ))) and similar proposed solution but resulting strings from those does not turn out to be a valid base64 encodings of the jpeg as it seem (I try the result from the conversions in online base64->image services and no image is shown). I have also tried other proposed solution such as base64-js and js-base64 libraries and non of those create a valid base64 valid image that can be shown in my React code.

How do you convert jpeg binary data to valid Base64 image encoding when btoa throws latin1 exception?

You've said you're using axios.get to get the image from the server. What you'll presumably get back will be a Buffer or ArrayBuffer or Blob , etc., but it depends on what you do with the response you get from axios .

I don't use axios (never felt the need to), but you can readily get a data URI for binary data from the server via fetch :

// 1.
const response = await fetch("/path/to/resource");
// 2.
if (!response.ok) {
    throw new Error("HTTP error " + response.status);
}
// 3.
const buffer = await response.arrayBuffer();
// 4.
const byteArray = new Uint8Array(buffer);
// 5.
const charArray = Array.from(byteArray, byte => String.fromCharCode(byte));
// 6.
const binaryString = charArray.join("");
// 7.
const theImage = btoa(binaryString);

Or more concisely:

const response = await fetch("/path/to/resource");
if (!response.ok) {
    throw new Error("HTTP error " + response.status);
}
const buffer = await response.arrayBuffer();
const binaryString = Array.from(new Uint8Array(buffer), byte => String.fromCharCode(byte)).join("");
const theImage = btoa(binaryString);

Here's how that works:

  1. We request the image data.
  2. We check that the request worked ( fetch only rejects its promise on network errors, not HTTP errors; those are reported via the status and ok props.
  3. We read the body of the response into an ArrayBuffer ; the buffer will have the binary image data.
  4. We want to convert that buffer data into a binary string . To do that, we need to access the bytes individually, so we create a Uint8Array (using that buffer) to access them.
  5. To convert that byte array into a binary string, we need to convert each byte into its equivalent character, and join those together into a string. Let's do that by using Array.from and in its mapping function (called for each byte), we'll use String.fromCharCode to convert the byte to a character. (It's not really much of a conversion. The byte 25 [for instance] becomes the character with character code 25 in the string.)
  6. Now we create the binary string by joining the characters in that array together into one string.
  7. Finally, we convert that string to Base64.

Looking at the docs, it looks like axios lets you provide the option responseType: "arraybuffer" to get an array buffer. If I'm reading right, you could use axios like this:

const response = await axios.get("/path/to/resource", {responseType: "arraybuffer"});
const binaryString = Array.from(new Uint8Array(response.body), v => String.fromCharCode(v)).join("");
const theImage = btoa(binaryString);

Fetch your image as a Blob and generate a blob:// URI from it.

data:// URLs are completely inefficient and require far more memory space than blob:// URLs. The data:// URL takes 34% more space than the actual data it represents and it must be stored in the DOM + decoded as binary again to be read by the image decoder. The blob:// URI on the other hand is just a pointer to the binary data in memory.

blob:// URLs are not perfect, but until browsers implement srcDoc correctly, it's still the best we have.

So if as per the comments you are using axios in a browser, you can do

const blob = await axios.get("/path/to/resource", {responseType: "blob"});
const theImage = URL.createObjectURL(blob);

And if you want to use the fetch API

const response = await fetch("/path/to/resource");
if (!response.ok) {
    throw new Error("HTTP error " + response.status);
}
const blob = await response.blob();
const theImage = URL.createObjectURL(blob);

Ps:
If you don't have any particular reason to fetch this image through AJAX (eg credentials or special POST params), then pass directly the URL of the resource as the src of your image:

<img src="/path/to/resource" />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM