I am curious as to what browsers (Specifically chrome) are doing when you set an img tags src to a path which returns a png binary string vs. if you would call this endpoint manually (Say using Ajax or Axios) and then set the images src to this PNG binary manually. Because the results are different between the two.
I am running a Node/expressJs server, which has an image file which returns from the images/ path.
router.get('images/', async function(req, res) {
try {
return await fs
.createReadStream('./images/sample.png')
.pipe(res);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
This API call will return a PNG binary string.
scenario 1 - setting the img src to the endpoint where the PNG lives
If I will set the img element as such <img src="http://localhost/images/"
This call will return the PNG binary from my Node server and automatically display everything correctly.
scenario 2 - Using axios to request this PNG manually
If however, I would use a library like axios, to call this endpoint manually, I would get back a PNG binary string (In the chrome dev-tools 'network' tab, when looking at repsonse data, I even see a nice preview thumbnail of the image, so something in that panel is also correctly taking the PNG data and displaying it in an img element there). But if I will take this data and try to set it to the src of the image tag, I need to do all kinds of base64 conversions before it will work.
So my question is - What is the internal algo of the img element, for when it receives binary image data from a server?
There is no 'internal algorithm'. src is an HTML attribute that expects a URI, whether it is an HTTP URI or a Base64 URI. The reason you have to convert the data to Base64 if you request the image using JavaScript is because this is the most straightforward way to represent binary data as a URI. When the browser sees a Base64 URI, then it just converts it back into binary data and displays it.
Related
I have a status badge image that returns the HTTP code 503 when the respective service is offline (but the webserver is still there serving calls). Now opening the image URL directly will display the image properly, regardless of the underlying 503 error code. But using it inside an <img> tag shows the broken image icon. How can I prevent that while still allowing the image itself to return a 503? (External services depend on that)
Here are some screenshots to illustrate what's going on:
The badge on the page:
The status message in the developer console:
The badge itself:
Note: This happens on Firefox. Not Chrome
Edit: Here are a few requested pieces information:
Firefox 78.0.2 (64-Bit)
It's served from the same domain. But the domain is essentially just proxying serveral underlying webservices. And this badge is originating from a different service but all on the same domain.
It's a SVG image if that makes any difference.
Since XMLHttpRequest can retrieve the output of any request, no matter the response code, it is possible to request for the image with XMLHttpRequest, and then convert the blob response type to a base64 format image, which can be loaded in the browser.
The CORS proxy I used in the sample code may not be necessary in the majority of cases, but could be useful in the case where the image you are trying to display has weird response headers that prevent access to the image from another domain.
Here is the sample code. It should work no matter the response code, CORS, etc.
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
// here, reader.result contains the base64-formatted string you can use to set the src attribute with
document.getElementsByTagName('img')[0].src = reader.result; // sets the first <img> tag to display the image, change to the element you want to use
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', "https://cors-anywhere.herokuapp.com/i.stack.imgur.com/8wB1j.png"); // don't include the HTTP/HTTPS protocol in the url
xhr.responseType = 'blob';
xhr.setRequestHeader('X-Requested-With', 'xhr');
xhr.send();
<img src="about:blank">
Everything works, as when you go into Inspect Element, you see that the src attribute of the <img> tag points to a base64 URL that can load in any browser.
You might want to compress or resize your images before uploading it to server , as they might be large enough to keep the server busy and show the error as most of the time, a 503 error occurs because the server is too busy.
More over the image is SVG so it might render dimesions before completing, hence I'd suggest
Try replacing the SVG with PNG or JPG
Also try for site like https://tinypng.com/ to compress the image size
This might work for you
I'm trying to put "-\/\/- Some URL for image"
inside of a string but I have a problem with the "-\/\/-", somebody can help me with that?
This looks like you're getting an image URL in a JSON response, which contains a property similar to this:
{
"imageUrl": "\/\/cdn.apixu.com\/weather\/64x64\/day\/116.png"
}
Don't worry about the backslash-escaped / characters, that's just an artifact of the JSON encoding, and any JSON parser you use on iOS will remove those, yielding a String value of
//cdn.apixu.com/weather/64x64/day/116.png
Now, a URL starting with just // is called a protocol-relative URL, so you'll have to prepend whatever protocol you used to get the initial JSON response, most likely https:. This gives you
https://cdn.apixu.com/weather/64x64/day/116.png
which indeed is a 64x64 pixel image that you can download and display in your app:
I'm trying to write an application in c++ using Qt 5.7, basically it should be a websocket server, using qwebsocket for this, capable to send an image elaborated with opencv to an HTML client. What I'm trying to do is encode the image in base64, transmit and on the client put the encoded string in the src of an image tag.
Just to test, I can send/receive text messages correctly, so the websocket architecture is working, but I have some problems with images. This is my code snippets:
Server
cv::Mat imgIn;
imgIn = cv::imread("/home/me/color.png",CV_LOAD_IMAGE_COLOR);
QByteArray Img((char*)(imgIn.data),imgIn.total()*imgIn.elemSize());
QByteArray Img64 = Img.toBase64();
pClient->sendBinaryMessage(Img64);
Client
<img id="ItemPreview" src="" style="border:5px solid black" />
....
websocket.binaryType = "arraybuffer";
websocket.onmessage = function (evt) {
console.log( "Message received :", evt.data );
document.getElementById("ItemPreview").src = "data:image/png;base64," + evt.data;
};
I think most of the problems are in the Server, because the base64 sequence I got from the image is different from the one I can get from online converter image/base64.
On the client I receive this error in the console and nothing is showed:
data:image/png;base64,[object ArrayBuffer]:1 GET
data:image/png;base64,[object ArrayBuffer] net::ERR_INVALID_URL
Any hints?
SOLUTION
Thanks to the suggestions, I can provide the working code:
Server
imgIn = cv::imread("/home/me/color.png", CV_LOAD_IMAGE_UNCHANGED);
std::vector<uchar> buffer;
cv::imencode(".png",imgIn,buffer);
std::string s = base64_encode(buffer.data(),buffer.size());
pClient->sendTextMessage(QString::fromStdString(s));
Client
Removed this line:
websocket.binaryType = "arraybuffer";
The base64 encoding in the server is done using this code:
Encode/Decode base64
This line in the server:
imgIn = cv::imread("/home/me/color.png",CV_LOAD_IMAGE_COLOR);
decodes a PNG formatted image, and places it in memory as load of pixel data (plus possibly some row padding, which you don't take account of, see below). That's what you're base64 encoding.
This line in the client:
document.getElementById("ItemPreview").src = "data:image/png;base64," + evt.data;
is expecting a PNG image, but that isn't what you're sending; you've just pushed out a load of raw pixel data, with no dimensions or stride or format information or anything else.
If your client wants a PNG, you're going to have to use something like imencode to write PNG data to a memory buffer, and base64 encode that instead.
One other important thing to note is that decoded images may have row padding... a few bytes on the end of each row for memory alignment purposes. Therefore, the actual length of each image row may exceed the width of the image multiplied by the size of the each pixel in bytes. That means that this operation:
QByteArray Img((char*)(imgIn.data),imgIn.total()*imgIn.elemSize());
may not, in fact, wrap the entire image buffer in your QByteArray. There are various ways to check the stride/step of an image, but you'd best read the cv::Mat docs as it isn't worth me repeating them all here. This only matters if you're doing raw byte-level image manipulation, like you are here. If you use imencode, you don't need to worry about this.
I try to rename file with download attribute but it's not working.
OK
FIDDLE
It only works if the file is on the same origin so if you can download a external file with CORS + ajax then you can save the blob with a custom name
$('a').click(function(evt){
evt.preventDefault();
var name = this.download;
// we need a blob so we can create a objectURL and use it on a link element
// jQuery don't support responseType = 'blob' (yet)
// So I use the next version of ajax only avalible in blink & firefox
// it also works fine by using XMLHttpRequest v2 and set the responseType
fetch("https://crossorigin.me/" + this.href)
// res is the beginning of a request it only gets the response headers
// here you can use .blob() .text() .json or res.arrayBuffer() depending
// on what you need, if it contains Content-Type: application/json
// then you might want to choose res.json()
// all this returns a promise
.then(res => res.blob())
.then(blob => {
$("<a>").attr({
download: name,
href: URL.createObjectURL(blob)
})[0].click();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
OK
From the docs you linked:
If the HTTP header Content-Disposition: is present and gives a different filename than this attribute, the HTTP header has priority over this attribute.
My guess is that the server you're linking to sets this header.
Also if you're linking to an external resource it likely won't work:
This attribute is only honored for links to resources with the same-origin.
It appears that this attribute doesn't work anymore for external files, due to possible security concerns.
You can find a discussion about this issue for Chrome here
Use of the 'download' attribute will always trigger a download, but from M-35 onwards will only honor the suggested filename if the final resource URL is same-origin as the document. Even when it doesn't, as long as a MIME type is specified correctly, it will receive a filename like 'download.' where is the extension known to the host OS as mapping to the specified MIME type. If the resource is served with a Content-Disposition, then the Content-Disposition will take precedence.
And for Firefox here and here
I need to set some headers when getting an image. The img src attribute does not allow this, so I'm using an XHR request to get the image. However, when I set the src attribute on the img tag after that request completes, it looks like the request is triggered again. Is there a way to cache the image and not trigger the second request?
Sample Code:
$(document).ready(function() {
var url = 'https://i.imgur.com/bTaDhpy.jpg'
var file = $.get(url);
file.then(function(data) {
$('#foo').attr('src', url);
});
});
JS Fiddle: https://jsfiddle.net/mehulkar/o4Lcs5Lo/
Note: my question is not about how to set the appropriate headers in the xhr request. My question is how to not trigger another GET from the setting of the src attribute and use the response from the XHR to display the image.
Use the $.ajax for this:
var myImg = $('#foo'),
mySrc = 'https://i.imgur.com/bTaDhpy.jpg';
$.ajax({
url: mySrc,
type: "GET",
headers: {
"X-TOKEN": 'xxxxx'
}
}).done(function() {
myImg.attr('src', mySrc); // set the image source
}).fail(function() {
myImg.hide(); // or something other
});
I cannot comment here due to reputation points, but here is what I've found on this.
I have a local html page that I'm executing via file:///
I can use a $.get to dynamically pull svg files into a variable. The debugger shows this as if it were a standard html node <svg. (I'm using Firefox Developer, but also in Firebug I see the svg file as a node.).
So At this point I have an empty <img that I want to set to my svg file I just don't know how to set the src attribute to the actual document itself. I suppose I could encode to base64.. You might be able to set it using the debugger itself. I couldn't get this to work reliably. Another avenue (for me since I'm using svg) is to clone the object then write it node for node. If you're not using svg perhaps there is some similar hack you could conduct with canvas? Load the image as a sprite then read the colors and set pixels?