简体   繁体   中英

Angular 5 how to display base64 encoded string as jpeg

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);
  }
}

The goal is, to be able to get a Base64 image from a JSON object and display the image. Often images are stored in Base64 encoding on databases, and a REST service is used to access the DB to return data to the web application. The JSON object used in this example simulates the sort of payload a REST API might provide.

This was created in Angular 9:

app.module - yours will not be the same, the HttpClientModule import is the important part here.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ImageComponent } from './image/image.component';

@NgModule({
  declarations: [
    AppComponent,
    ImageComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html - just a place to stick the demo

<app-image></app-image>

image-model.ts - a simple data model, note the SafeResourceUrl import:

import { SafeResourceUrl } from '@angular/platform-browser';

export class ImageRecord {
    
    public _name: string;
    public _imgUrlSafe: SafeResourceUrl;

    constructor(name: string,
                imgUrlSafe?: SafeResourceUrl)
    {
        this._name = name;
        if (imgUrlSafe)
        {
            this._imgUrlSafe = imgUrlSafe;
        }
        else
        {
            this._imgUrlSafe = null;
        }
        

    }
}

image.service.ts - a simple service to use HttpClient to get the JSON as though you were making a real service call. Note the use of DomSanitizer.bypassSecurityTrustUrl() to format the Base64 image data so it can be rendered correctly.

import { Injectable} from '@angular/core';
import { ImageRecord} from './image-model';
import { HttpClient} from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { DomSanitizer } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root'
})
export class ImageService {

private _imageRecord: ImageRecord;

constructor(private _httpClient: HttpClient,
            private _domSanitizer: DomSanitizer) { }
  
public sendGetRequestNoHeaders(getUrl: string){
  return this._httpClient.get(getUrl, {observe: 'response'});
}
   
loadImageRecordsFromJson()
{
  console.log("loadImageRecordsFromJson");

  return this.sendGetRequestNoHeaders('../../../assets/image.json').pipe(tap(
  (response => 
    {

      console.log("response.body: " + response.body);

      this._imageRecord = new ImageRecord(response.body['myimage'].name,
                                          this._domSanitizer.bypassSecurityTrustUrl('data:image/jpeg;base64,' +  response.body['myimage'].ImgBase64)
      );
    }
  )));
}


getImage()
{
  return this._imageRecord;
}

}

image.component.ts - get the record from the service.

import { Component, OnInit } from '@angular/core';
import { ImageRecord } from './image-model';
import { ImageService } from './image.service'

@Component({
  selector: 'app-image',
  templateUrl: './image.component.html',
  styleUrls: ['./image.component.css']
})
export class ImageComponent implements OnInit {

  public _imageRecord: ImageRecord;
  constructor(private _imageService: ImageService) { }

  ngOnInit(): void {
    this._imageService.loadImageRecordsFromJson().subscribe(() => {
      this._imageRecord = this._imageService.getImage();
    });
  }

}

image.component.html - display the record.

<h1>Your Image From Base64 and JSON</h1>

<ng-container *ngIf="_imageRecord">
    <div class="float-left">
        <h4 class="list-group-item-heading">{{_imageRecord._name}}</h4>
    </div>

    <span class="float-right">
    <img [src]="_imageRecord._imgUrlSafe"          
    alt="where is my image?" 
    class="img-responsive" 
    style="max-height: 40px; max-width: 40px;"
    >
    </span>
</ng-container>

src/assets/image.json - a single record with a name and Base64 image.

{
    "myimage":
    {
        "name": "One",
        "ImgBase64":"/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAAoACgDAREAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAYFAv/EABkBAQADAQEAAAAAAAAAAAAAAAAEBQYBCP/aAAwDAQACEAMQAAABk8j6FAAAAHPeS0/J1dfrQBkSafXjXAAAA//EABwQAAEDBQAAAAAAAAAAAAAAAAQDETABAgUTIP/aAAgBAQABBQKKrttMQyHBIl6x0H//xAAhEQABAwMEAwAAAAAAAAAAAAABAwQRAiEwAAUTUSAxgf/aAAgBAwEBPwHEZi2ubcEHyCS6gIrmwHQ79+Llmos8QcUm1Ez9w//EABsRAAICAwEAAAAAAAAAAAAAAAIDATASIDFR/9oACAECAQE/Aa8VEopGOagyBWQe0//EAB4QAAEDBAMAAAAAAAAAAAAAAAIBAxEEEiEwACAx/9oACAEBAAY/AtWPeUrbrwkLsyIDjrSvIqWtXTOn/8QAHBAAAwABBQAAAAAAAAAAAAAAAREhMAAgMUFR/9oACAEBAAE/IcVsRHxozTqoBD03aE9kkWWOsP8A/9oADAMBAAIAAwAAABBttttuBtsNttt//8QAHhEBAAIBBAMAAAAAAAAAAAAAAREhMQAgMEFRYXH/2gAIAQMBAT8Q4rnfqcT702RnGBZlKt8mNpuCuZlhBFJ9lOH/xAAcEQACAgIDAAAAAAAAAAAAAAABESEwAFEgMYH/2gAIAQIBAT8QqCc428FJJ2ddcTSEovKf/8QAHBABAQABBQEAAAAAAAAAAAAAARFhACEwMUEg/9oACAEBAAE/EOLI1neXGk9LOpchK3T06+WQzGJYQCPW9Th//9k="
    }
}

a better way is to return byte[] from the server and use it like below

getImage(imageId: string): Observable<ImageSource> {
    return this.http.get(`${dashboardImagesUrl}/${imageId}`, { responseType: 'blob', observe: 'response' }).map((response) => {
      if (response) {
        const headers = response.headers;
        const blob = new Blob([response.body], { type: headers.get('content-type') });
        const urlCreator = window.URL;

        return {
          width: Number(response.headers.get('X-Image-Width')),
          height: Number(response.headers.get('X-Image-Height')),
          backgroundImage: this._sanitizer.bypassSecurityTrustUrl(urlCreator.createObjectURL(blob))
        };
      }
    });
  }

HTML

<img [src]="imageSource?.backgroundImage">

set image height, width as headers from the server

below server code might not be useful for you but for java-spring people below will be useful

Java backend

public class ImageEntity {

    @Id
    private String id;

    private byte[] imageByteArr;

    private int height;

    private int width;

    public static ImageEntity create(MultipartFile file) {
        try {
            final BufferedImage image = ImageIO.read(file.getInputStream());
            if(image == null) {
                log.error("Can't convert the file to a buffered image.");
                throw new ImageProcessingException();
            }
            final ImageEntity entity = new ImageEntity();
            entity.setImageByteArr(file.getBytes());
            entity.setHeight(image.getHeight());
            entity.setWidth(image.getWidth());
            return entity;
        } catch (IOException exp) {
            log.error("Exception while converting image file:", exp);
            throw new ImageProcessingException(exp);
        }
    }

}

controller

@RequestMapping(
            path = "/{id}",
            method = RequestMethod.GET,
            produces = { MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE })
    public ResponseEntity<byte[]> get(@PathVariable String id) {
        final ImageEntity entity = this.repository.findById(id).get(); // i'm reading from mongodb
        final HttpHeaders headers = new HttpHeaders();
        headers.setCacheControl(CacheControl.noCache().getHeaderValue());
        headers.set(X_IMAGE_WIDTH_HEARDER, String.valueOf(entity.getWidth()));
        headers.set(X_IMAGE_HEIGHT_HEADER, String.valueOf(entity.getHeight()));
        return new ResponseEntity<>(entity.getImageByteArr(), headers, HttpStatus.OK);
    }

while saving just use

this.repository.save(ImageEntity.create(file));

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