I am trying to display a base64 encoded string as jpeg. On the server (C#), I am just returning the encoded string (just hard coding for test purposes, ignoring the passed parameter):
[HttpGet("[action]")]
public string GetImage(string imageInfo)
{
// get the path to the image, using the passed imageInfo
var imgPath = @"C:\SmallFiles\TestLogo.jpg";
// return base64 string
byte[] imageArray = System.IO.File.ReadAllBytes(imgPath);
string base64ImageRepresentation = Convert.ToBase64String(imageArray);
return base64ImageRepresentation;
}
When I take that string (base64ImageRepresentation) and paste it into a text-to-jpeg converter (https://onlinejpgtools.com/convert-base64-to-jpg), the image is displayed properly, and the encoded string starts with this: "/9j/4". And I can verify that this is in fact being returned to the browser, using Chrome's Network tab.
Now, here is my Angular 5 component:
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
baseUrl: string;
sanitizedImageSrc: SafeResourceUrl;
constructor(private route: ActivatedRoute, private _http: HttpClient, @Inject('BASE_URL') baseUrl: string, private _sanitizer: DomSanitizer) {
this.baseUrl = baseUrl;
}
ngOnInit() {
// if image information is being passed to the page, get the parameter information
let imageLookupInfo = this.route.snapshot.queryParamMap.get('imgInfo');
if (imageLookupInfo)
this.getImage(imageLookupInfo).subscribe(
data => {
this.createImageFromBlob(data);
},
error => console.error(error));
}
createImageFromBlob(image: Blob) {
let reader = new FileReader();
reader.addEventListener("load", () => {
let result = reader.result;
let imgString = result.toString().replace('text/plain', 'image/jpeg');
this.sanitizedImageSrc = this._sanitizer.bypassSecurityTrustResourceUrl(imgString);
}, false);
if (image) {
reader.readAsDataURL(image);
}
}
getImage(imageLookupInfo: string): Observable<Blob> {
return this._http.get(this.baseUrl + 'api/SampleData/GetImage?imageInfo=' + imageLookupInfo, { responseType: "blob" });
}
}
And my HTML:
<div class='row'>
<div class='col-lg-10 col-md-10 col-sm-10 col-xs-10'>
<h1>Test Image Display</h1>
<div *ngIf="sanitizedImageSrc">
<img [src]="sanitizedImageSrc" width="100" height="50">
</div>
</div>
</div>
In the createImageFromBlob method, I find that the image Blob always has a type of "text/plain":
Blob(26544) {size: 26544, type: "text/plain"}
The type property is readonly, so I can't modify it. Instead, I wait until the blob is converted to a string via the FileReader's "readAsDataURL" method, and then I do a replace with "image/jpeg" (please let me know if there is a better way), which gives me an imgString that starts like this:
"data:image/jpeg;base64,LzlqLzRBQVFTa1pKUmdBQkFRRUF"
Note how the data now starts with "Lzlq" rather than "/9j/4". Why would that be??? And when I copy the full text of the encoded data and paste it into the same text-to-jpeg converter (https://onlinejpgtools.com/convert-base64-to-jpg), I see no image at all, which is exactly what shows in my web page - nothing at all. No errors, just no image.
What am I doing wrong? What is happening here? It seems like this should be so easy. Does anybody have a working example of how to do this?
Any help would be most appreciated.
Thank you, -- John
UPDATE: I made this work with a base64 encoded string as follows. This does not answer the question of why the Blob approach did not work for me, and I'd still like to know, but this approach worked for me, in case somebody finds it useful.
1) Modify the server side to return an ImageInfo object defined like this:
public class ImageInfo
{
public string FileExtension { get; set; }
public string Base64EncodedContent { get; set; }
}
So the server-side code now looks like this:
[HttpGet("[action]")]
public ImageInfo GetImageInfo(string imageInfo)
{
// get the path to the image, using the passed imageInfo
var imgPath = @"C:\SmallFiles\TestLogo.jpg";
// get image as base64 string
byte[] imageArray = System.IO.File.ReadAllBytes(imgPath);
string base64ImageRepresentation = Convert.ToBase64String(imageArray);
var info = new ImageInfo();
info.FileExtension = "jpeg";
info.Base64EncodedContent = base64ImageRepresentation;
return info;
}
Then the angular component is modified to just use the two properties of the returned object, defined in this interface:
export interface ImageInfo {
fileExtension: string;
base64EncodedContent: string;
}
And the component looks like this (just proof of concept code - not a production-worthy implementation):
import { Component, Inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { ImageInfo } from '../interfaces/imageInfo';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
baseUrl: string;
sanitizedImageSrc: SafeResourceUrl;
constructor(private route: ActivatedRoute, private _http: HttpClient, @Inject('BASE_URL') baseUrl: string, private _sanitizer: DomSanitizer) {
this.baseUrl = baseUrl;
}
ngOnInit() {
// if image information is being passed to the page, get the parameter information
let imageLookupInfo = this.route.snapshot.queryParamMap.get('imgInfo');
if (imageLookupInfo)
this.getImageInfo(imageLookupInfo)
.subscribe(
imageInfo => {
this.sanitizedImageSrc = this._sanitizer.bypassSecurityTrustResourceUrl('data:image/' + imageInfo.fileExtension + ';base64,' + imageInfo.base64EncodedContent);
},
error => console.error(error)
);
}
getImageInfo(imageLookupInfo: string): Observable<ImageInfo> {
return this._http.get<ImageInfo>(this.baseUrl + 'api/SampleData/GetImageInfo?imageInfo=' + imageLookupInfo);
}
}