I had some time this morning to investigate this PNG to Base64 business further.
Although I now have something that works, it is not pretty or fast, and certainly not optimized. Here it is for anyone interested, I will try to improve on it when I have more time.
Code:
BinaryString();
Fmt8Bin();
EXPORT base64img()
BEGIN
LOCAL mylst,mystr:="",mylen,mysubstr,newlst,i,j,b64str:="";
LOCAL fname:="b64img.txt",fsize,fiter,fcontent,padsz,cur_chunk;
LOCAL dict:={"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/"};
AFiles(fname):=G0;
fsize:=AFilesB(fname);
IF fsize>10000 THEN // lists can not be longer than 10000 items so it's necessary to process in chunks if longer
fiter:=IP(fsize/10000);
FOR j FROM 1 TO fiter DO
fcontent:=AFilesB(fname,(j-1)*10000,10000);
mystr:=mystr+BinaryString(fcontent);
END;
IF FP(fsize/10000) THEN
fcontent:=AFilesB(fname,fiter*10000,fsize-(fiter*10000));
mystr:=mystr+BinaryString(fcontent);
END;
ELSE
fcontent:=AFilesB(fname,0,fsize);
mystr:=mystr+BinaryString(fcontent);
END;
mylen:=DIM(mystr);
FOR i FROM 1 TO IP(mylen/24) DO // iterate over 24-bit increments, construct the 6-bit integers as strings
mysubstr:=MID(mystr,(i-1)*24+1,24);
newlst:=EXPR("{#0"+MID(mysubstr,1,6)+"b,#0"+MID(mysubstr,7,6)+"b,#0"+MID(mysubstr,13,6)+"b,#0"+MID(mysubstr,19,6)+"b}");
newlst:=SETBASE(newlst,3);
b64str:=b64str+dict(newlst(1)+1)+dict(newlst(2)+1)+dict(newlst(3)+1)+dict(newlst(4)+1);
END;
IF FP(mylen/24) THEN // deal with partial remainders of less than 24 bits
mysubstr:=MID(mystr,(i-1)*24+1);
padsz:=6-(DIM(mysubstr) MOD 6);
IF padsz THEN
FOR i FROM 1 TO padsz DO
mysubstr:=mysubstr+"0";
END;
END;
newlst:="{";
FOR i FROM 1 TO DIM(mysubstr)/6 DO
newlst:=newlst+IFTE(i>1,",","");
newlst:=newlst+"#0"+MID(mysubstr,(i-1)*6+1,6)+"b";
END;
newlst:=EXPR(newlst+"}");
newlst:=SETBASE(newlst,3);
FOR i FROM 1 TO SIZE(newlst) DO
b64str:=b64str+dict(newlst(i)+1);
END;
padsz:=4-(SIZE(newlst) MOD 4);
IF padsz THEN // pad if necessary
FOR i FROM 1 TO padsz DO
b64str:=b64str+"=";
END;
END;
END;
DelAFiles(fname);
fsize:=DIM(b64str);
IF fsize>10000 THEN // again max of 10000
fiter:=IP(fsize/10000);
FOR j FROM 1 TO fiter DO
cur_chunk:=ASC(MID(b64str,(j-1)*10000+1,10000));
AFilesB(fname,(j-1)*10000):=cur_chunk;
END;
IF FP(fsize/10000) THEN
cur_chunk:=ASC(MID(b64str,fiter*10000+1));
AFilesB(fname,fiter*10000):=cur_chunk;
END;
ELSE
AFilesB(fname,0):=ASC(b64str);
END;
END;
// returns a string of sequential binary integers,
BinaryString(declist)
BEGIN
LOCAL binlist,k,binstr:="";
binlist:=SETBASE(declist,1);
FOR k FROM 1 TO SIZE(declist) DO
binstr:=binstr+Fmt8Bin(STRING(binlist(k)));
END;
RETURN binstr;
END;
// removes # and b characters from the string of the binary integer
// left pads with 0's if not 8 characters
Fmt8Bin(str)
BEGIN
LOCAL k,strdim;
str:=MID(str,2,DIM(str)-2);
strdim:=DIM(str);
IF strdim<8 THEN
FOR k FROM strdim+1 TO 8 DO
str:="0"+str;
END;
END;
RETURN str;
END;
It appears that the bulk of the time is spent formatting the binary strings, padding with 0's, etc.