Passion & Opportunity ? continue : break

How to convert image file to base64 for <img> tag?
Written: 2019-04-01 15:09:56 Last update: 2019-04-19 12:28:29

Base64 encoded text can be displayed in <img> tag using the format as:

<img src='data:[mime-type];base64,[encoded-text]' />
  • [mime-type], must be either one of values stated in IANA Media Type - Image
    Common values are: 'image/jpeg', 'image/png', 'image/gif' and 'image/svg+xml'
  • [encoded-text] is the base64 long text

Demo ..

  1. Select an image file
    Please drag and drop an image file here OR use the button to select an image file.



    Mimetype:

    Image resolution:

    File size:

    (from canvas, maybe resized) image base64 encoded text length:

    Image base64 encoded text content:
  2. Display base64 encoded text to <img> tag
    Click the button to convert base64 encoded text above to view the image below,
    * (you can copy and paste the base64 encoded text from database or other source)
    .

The above demo using very simple Javascript code below, please take a look at it as it is very simple and self-explanatory.

function dragOverHandler(event) {
  console.log('dragOverHandler(event)');

  // prevent object dragging around this area or file from being open by browser
  event.preventDefault();
}
function dropHandler(event) {
  console.log('dropHandler(event)');

  // prevent file from being open by browser
  event.preventDefault();

  if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
    // has at least 1 file, so read it 
    readImageFile(event.dataTransfer.files);
  }
}
function displayBase64EncodedTextToImg() {
  let ele = document.getElementById('canvas_data_url_base64_encoded_text');
  let base64value = ele.value.trim();
  if(base64value == null || base64value.length < 1) {
    console.log('no value');
    return;
  }

  document.getElementById('img_from_base64').src = base64value;
}

function getImageProperty(binaryData, callback) {
  if(! callback) {
    console.log('getImageProperty(binaryData, callback), error: callback is null or undefined');
    return null;
  }
  if(binaryData == null || binaryData.length < 4) {
    callback('{success: false, errMsg: \'checkImage(binaryData), empty or invalid length\'}');
    return null;
  }

  // get 'magic numbers' (first 4 bytes value)
  let hexSignature = '';
  for(let i = 0; i < 4; i++) {
    let val = binaryData.charCodeAt(i).toString(16);
    hexSignature += (val.length < 2 ? '0' + val : val);
  }

  let mimeType = getImageMimeType(hexSignature);
  if(mimeType == null || mimeType.length < 1) {
    callback('{success: false, errMsg: \'check mime type failed, maybe not an image data\'}');
    return null;
  }

  let imageProperty = {};
  imageProperty.success = true;
  imageProperty.binaryDataSize = binaryData.length;
  imageProperty.mimeType = mimeType;
  imageProperty.signature = hexSignature;
  imageProperty.imgEncodedBase64 = 'data:' + mimeType + ';base64,' + btoa(binaryData);

  if(mimeType.length > 0) {
    let myImg = new Image();

    // read ACTUAL image width & height
    myImg.onload = function() {
      imageProperty.width = myImg.naturalWidth;
      imageProperty.height = myImg.naturalHeight;
      imageProperty.ImageObject = myImg;

      callback(imageProperty);
    }

    // give data
    myImg.src = imageProperty.imgEncodedBase64;
  }
}

function getImageMimeType(hexSignature) {
  let type = '';
  switch(hexSignature) {
  case '89504e47':
      type = 'image/png';
      break;
  case '47494638':
      type = 'image/gif';
      break;
  case 'ffd8ffe0':
  case 'ffd8ffe1':
  case 'ffd8ffe2':
  case 'ffd8ffe3':
  case 'ffd8ffe8':
      type = 'image/jpeg';
      break;
  default:
      type = '';//[unknown]';
  }
  return type;
}

function readImageFile(files) {
  if(files == null) {
    return;
  }

  // only for demo, get the first file
  let f = files[0];

  console.log('readImageFile(FileList), files[0] filename: ' + f.name);

  let isImg = false;

  // for demo, only parse these 4 mimetypes
  switch(f.type) {
  case 'image/png':
  case 'image/jpg':
  case 'image/jpeg':
  case 'image/gif':
      isImg = true;
      break;
  }

  if(! isImg) {
    alert('File "' + f.name + '" does not have an image extention (*.jpeg, *.jpg, *.png, *.gif)');
    return;
  }

  // check proper mimeType

  const reader = new FileReader();
  reader.onload = (function(theFile) {
    console.log('reader loaded');
    return function(e) {
      let imageProperty = getImageProperty(e.target.result, function(result) {
        if(! result || !result.success) {
          alert('Failed to parse the image property of "' + f.name + '", maybe the file is not an image file.');
          return;
        }

        let canvas = document.getElementById('canvasImage');
        if(! canvas) {
          return;
        }
        let context = canvas.getContext('2d');
        context.mozImageSmoothingEnabled = false; // Gecko

        // simple and fast BICUBIC resize
        // use parameter maxWidth and maxHeight ... because canvas.width and canvas.height can be changed later (not suitable)
        var MAX_WIDTH = parseInt(canvas.style.maxWidth); // ie: convert from '200px' to 200 (value)
        var MAX_HEIGHT = parseInt(canvas.style.maxHeight);

        // do we need to resize image ?
        if(MAX_WIDTH > 0 && MAX_HEIGHT > 0) {
          var tempW = result.width;
          var tempH = result.height;

          var ratioWidth = MAX_WIDTH / tempW;
          var ratioHeight = MAX_HEIGHT / tempH;

          // find the lowest value if at least one is less than 1
          if(ratioWidth < 1 || ratioHeight < 1) {
            ratioDivider = (ratioWidth > ratioHeight ? ratioHeight : ratioWidth);

            tempW = Math.floor(tempW * ratioDivider);
            tempH = Math.floor(tempH * ratioDivider);
          } else {
              // image is smaller than canvas size
              ratioDivider = 1;
          }

          // resize canvas
          canvas.width = tempW;
          canvas.height = tempH;

          // draw it (will be visible on screen)
          context.drawImage(result.ImageObject, 0, 0, tempW, tempH);
        }

        // get the RESIZED image base64 data

        // store the data into input hidden field inside form to submit to server or send by ajax
        // JPEG compressed 80%, no transparency and transparent pixel will be black
        let canvasBase64EncodedText = getCanvasBase64Data(canvas, 'image/jpeg', 0.8);
        document.getElementById('canvas_data_url_base64_encoded_text').value = canvasBase64EncodedText;

        // below additional code to display to UI for this page
        document.getElementById('img_mime_type').innerText = result.mimeType;
        document.getElementById('img_resolution').innerText = result.width + ' * ' + result.height;
        document.getElementById('file_size').innerText = result.binaryDataSize;
        document.getElementById('base64_length').innerText = canvasBase64EncodedText.length;
      });
    }
  })(f);

  reader.readAsBinaryString(f);
}

function getCanvasBase64Data(eleCanvas, mimeType, compression) {
  if(! mimeType) {
    mimeType = 'image/png'; // default
    compression = 1.0; // for PNG there is no compression
  }
  let base64data = eleCanvas.toDataURL(mimeType, compression);
  return base64data;
}

We need to understand what is the use cases for this function and what is the benefit of implementing it.

PROs: (and use cases)

  • To show many small images (thumbnails or emoticons or others) in 1 page, sometimes better to save the images in a database and then display them in HTML using this embedded base64 encoded text data method to avoid too many image request connections from browser to image server or CDN.
  • If using web page which has JS script to wait for loading many images then using this logic will reduce JS waiting time
  • To prevent a direct connection from a browser or curl command to get the image files, this way will also ensure to display a landing webpage instead of giving the image data immediately.
  • To create image viewing counter logic, to know which images are most viewed.
  • To avoid many file operations to delete or update if the images are often updated, such as product thumbnails, profile avatar, etc.
  • For small project with limited capacity issue, we can store the image as base64 encoded text to different 'free' hosting and create an API to get the image, this is to create a 'free' CDN.

CONs:

  • The web page may have long base64 text and page source size will be bigger.
  • Browser may not be able to keep the image data in cache or if cached there is no way to tell 'expire' time.

To avoid the 'CONs' described above, we can create an API in server to return the image, using PHP is as simple as:

// set header control
header('Content-Type: '.$arr['thumbnailMimeType']);
header('Pragma: public');

// ask browser to cache for 24 hours (24 * 60 * 60 = 86400 seconds)
header('Cache-Control: max-age=86400');
header('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', time() + 86400));

// remove the 'image/jpeg' or whatever the mime type 
$data = $arr['thumbnailBase64Data'];
$signature = 'data:'.$arr['thumbnailMimeType'].';base64,';

echo base64_decode(substr($data, strlen($signature)));