1

Currently, I have an image stored as an MxNx3 uint8 array in MATLAB. However, I need to embed it in an HTML document, and I can't include the image separately.

Instead, I've decided to try and encode the image as a base64 string. However, I can't seem to find a way to encode the image as a string without having to first save the image to disk. I tried looking into writebmp and the like, but I can't seem to get it to work.

I'd really rather not write the image to a file, just to read it back using fread. The computer I'm using has very low Disk I/O, so that will take way too long.

Any help would be appreciated!

Edit:

I looked here, but that errors in R2018b due to "no method found". When I linearize the image, the returned string is incorrect

ArodPonyboy678
  • 168
  • 1
  • 8
  • Have you tried [this MathWorks File Exchange submission](https://www.mathworks.com/matlabcentral/fileexchange/39526-byte-encoding-utilities?focused=3773214&tab=function)? It uses Matlab's ability to call Java functions (provided you're not running in `-nojvm` terminal mode). – horchler Apr 11 '18 at 21:09
  • It errors for MxNx3 arrays, and when I linearize it, it gives the wrong answer... – ArodPonyboy678 Apr 12 '18 at 02:21

1 Answers1

1

From an image matrix to HTML

1 Convert the image to the bytes of a BMP

function [header] = writeBMP(IM)
header = uint8([66;77;118;5;0;0;0;0;0;0;54;0;0;0;40;0;0;0;21;0;0;0;21;0;0;0;1;0;24;0;0;0;0;0;64;5;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0]);
IMr = IM(:,:,1);
IMg = IM(:,:,2);
IMb = IM(:,:,3);clear IM;
IM(:,:,1)=IMb';
IM(:,:,2)=IMg';
IM(:,:,3)=IMr';
IM(:,:,:)=IM(:,end:-1:1,:);
[i,j,~]=size(IM);
header(19:22) = typecast(int32(i),'uint8'); %width
header(23:26) = typecast(int32(j),'uint8'); %height
IM = permute(IM,[3,1,2]);
IM = reshape(IM,[i*3,j]);
W = double(i)*3;
W = ceil(W/4)*4;
IM(3*i+1:W,:)=0; %padd zeros
IM = IM(:); %linear
header(35:38) = typecast(uint32(length(IM)),'uint8'); %datasize
header = [header;IM];
header(3:6) = typecast(uint32(length(header)),'uint8'); %filesize
end

You can also look into ...\toolbox\matlab\imagesci\private\writebmp.m for a more detailed example.

2 Encode the bytes to base64 characters

This is best done in a mex-file. Save this code as encodeB64.c and run mex encodeB64.c

/*==========================================================
 * encodeB64.c - converts a byte vector to base64
 *
 * The calling syntax is:
 *
 *      [B] = encodeB64(B)
 *
 *      input:   - B     : vector of uint8
 *
 *      output:  - B     : vector of base64 char
 *
 * This is a MEX-file for MATLAB.
 *
 *========================================================*/

#include "mex.h" 

/* The computational routine */
void Convert(unsigned char *in, unsigned char *out,unsigned long Nin, unsigned long Nout)
{
    int temp; 
    static unsigned char alphabet[64] = {65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,48,49,50,51,52,53,54,55,56,57,43,47};
    for (int i=0;i<(Nin-2);i+=3){
        temp = in[i+2] | (int)in[i+1]<<8 | (int)in[i]<<16;
        for (int j=0;j<4;j++){
            out[3+(i/3)*4-j] = alphabet[(temp >> (j*6)) & 0x3f];
        }
    }
    if (Nin%3==1){
        temp = (int)in[Nin-1]<<16;
        out[Nout-1] = 61;
        out[Nout-2] = 61;
        out[Nout-3] = alphabet[(temp >> 12) & 0x3f];
        out[Nout-4] = alphabet[(temp >> 18) & 0x3f];
    }
    if (Nin%3==2){
        temp = in[Nin-1]<<8 | (int)in[Nin-2]<<16;
        out[Nout-1] = 61;
        out[Nout-2] = alphabet[(temp >> 6) & 0x3f];
        out[Nout-3] = alphabet[(temp >> 12) & 0x3f];
        out[Nout-4] = alphabet[(temp >> 18) & 0x3f];
    }
}

/* The gateway function */
void mexFunction( int nlhs, mxArray *plhs[],int nrhs, const mxArray *prhs[])
{
    unsigned char *InputV;           /* input vector 1*/
    unsigned char *OutputV;          /* output vector 1*/
    unsigned long Nin;
    unsigned long Nout;
    
    /* check for proper number of arguments */
    if(nrhs!=1) {
        mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nrhs","One inputs required.");
    }
    if(nlhs!=1) {
        mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nlhs","One output required.");
    }
     /* make sure the first input argument is scalar integer*/
    if( !mxIsClass(prhs[0],"uint8") || mxGetNumberOfElements(prhs[0]) == 1 || mxGetN(prhs[0]) != 1)  {
        mexErrMsgIdAndTxt("MyToolbox:arrayProduct:notRowInteger","Input one must be uint8 column vector.");
    }
    
    /* get the value of the scalar input  */
    InputV = mxGetPr(prhs[0]);
    Nin = mxGetM(prhs[0]); /*number of input bytes */
    Nout = 4*((Nin+2)/3);
    
    /* create the output matrix */
    plhs[0] = mxCreateNumericMatrix((mwSize)Nout,1,mxUINT8_CLASS,mxREAL);
    
    /* get a pointer to the real data in the output matrix */
    OutputV = (unsigned char *) mxGetData(plhs[0]);
    
    /* call the computational routine */
    Convert(InputV,OutputV,Nin,Nout);
}

To test it you can run

T = randi(255,[2^28,1],'uint8'); %250MB random data
tic;Res=encodeB64(T);t=toc       %convert
(length(T)/2^20) / t             %read in MB/s
(length(Res)/2^20) / t           %write in MB/s

My result:

read: 467 MB/s write: 623 MB/s

3 Put it all together and test

file = 'test.html';
fid = fopen(file,'wt');
fwrite(fid,sprintf('<html>\n<header> </header>\n<body>\n'));
fwrite(fid,sprintf('<p>%s</p>\n','Show the Matlab demo image street1.jpg'));
IM = imread('street1.jpg');figure(1);clf;image(IM);
B = writeBMP(IM);
str = encodeB64(B);
fwrite(fid,sprintf('<img src="data:image/bmp;base64,%s"/>\n',str));
fwrite(fid,sprintf('</body>\n</html>'));
fclose(fid);

this should generate a 1,229,008 byte HTML file with an image encoded.

Community
  • 1
  • 1
Gelliant
  • 1,835
  • 1
  • 11
  • 23
  • I'll mark it as the answer once one of us actually figures it out completely, but thanks for the starting point! – ArodPonyboy678 Apr 16 '18 at 13:36
  • I think I got this figured out. – Gelliant Apr 18 '18 at 12:36
  • Yes it should. I updated the example to work for a matlab demo image. The writeBMP is a mess, but at least it works. – Gelliant Apr 18 '18 at 12:59
  • Agreed. I was going to ask (I just finished testing your earlier version) and it looked like it *almost* worked. But let me try your latest version and let's see what we get – ArodPonyboy678 Apr 18 '18 at 13:00
  • Honestly I can clean it up later but getting something that works is by far most important! – ArodPonyboy678 Apr 18 '18 at 13:00
  • Unfortunately, it appears to be slower than just using `fwrite` and reading back in the bytes. I suspect this is because the `fwrite` portion is actually using `C`, so it's much closer to the metal than MATLAB could ever be. – ArodPonyboy678 Apr 18 '18 at 13:25
  • Yeah, also, here we write a BMP, that is generally speaking a lot bigger than a .PNG since it does not use compression at all. So indeed `1) write png 2) read png 3) convert png 4) write html` might be faster than `1) convert bmp 2) write html` – Gelliant Apr 18 '18 at 14:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/169249/discussion-between-arod-ponyboy678-and-gelliant). – ArodPonyboy678 Apr 18 '18 at 14:04