Preguntaron como codificar y descodificar una imagen en BASE64 sin utilizar la API, como había hecho en este hilo, se trataba de usarlo en un entorno ajeno a Windows (Android). Al leer la pregunta no pude resistirme a implementar mi propia versión sin grandes pretensiones, usando pascal sin las facilidades de la VCL.
BASE64 se basa a usar los bytes de un buffer en conjuntos de 6 bits, de forma que el mayor número es el 111111b es decir, 63 en decimal. A cada valor obtenido se le asigna un carácter de un alfabeto de 64 caracteres. Si faltan bits se añaden ceros y se codifican con uno o dos caracteres '=' (según falten 8 ó 16 bits) para informar al decodificador. La decodificación es a la inversa.
El tamaño del buffer de salida codificado, cuando no se introducen retornos de carro y línea se puede calcular así: ((L+2) div 3)*4. Y cuando se decodifica el tamaño será (L*3) div 4. El decodificador debe ignorar cualquier carácter que no esté en el alfabeto.
Escribí el código en una unit que publico a continuación:
unit BASE64; interface type TAByte = Array of Byte; PAByte = ^TAByte; function Decode64(var S: String): TAByte; function Encode64(Bin: PByte; Count: integer): String; implementation const SB64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; IB64: array[#0..#255] of integer = ( $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $3E, $FF, $FF, $FF, $3F, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $00, $01, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $0B, $0C, $0D, $0E, $0F, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $FF, $FF, $FF, $FF, $FF, $FF, $1A, $1B, $1C, $1D, $1E, $1F, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F, $30, $31, $32, $33, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF ); type SAByte = Array[0..0] of Byte; PSAByte = ^SAByte; function Decode64(var S: String): TAByte; var B: array[0..3] of BYTE; L, n, i, j: integer; begin n:= 1; i:= 0; L:= Length(S); if L < 4 then exit; while S[L] = '=' do dec(L); SetLength(Result, (L*3) div 4); repeat for j:= 0 to 3 do begin while (n < L) and (IB64[S[n]] = $FF) do inc(n); B[j]:= IB64[S[n]]; inc(n); end; Result[i]:= (B[0] shl 2) or (B[1] shr 4); if B[2] = 255 then break; Result[i+1]:= (B[1] shl 4) or (B[2] shr 2); if B[3] = 255 then break; Result[i+2]:= (B[2] shl 6) or B[3]; inc(i, 3); until n > L; end; function Encode64(Bin: PByte; Count: integer): String; var B0, B1, B2: BYTE; ABin: PSAByte; L, n, i: integer; begin if Count = 0 then exit; ABin:= PSAByte(Bin); n:= 0; i:=1; L:= ((Count+2) div 3)*4; SetLength(Result, L); repeat B0:= ABin^[n]; B1:= 0; B2:= 0; if (Count - n) > 0 then B1:= ABin^[n+1]; if (Count - n) > 1 then B2:= ABin^[n+2]; Result[i]:= SB64[(B0 shr 2) and $003F +1]; Result[i+1]:= SB64[((B0 shl 4) or (B1 shr 4)) and $3F +1]; Result[i+2]:= SB64[((B1 shl 2) or (B2 shr 6)) and $3F +1]; Result[i+3]:= SB64[(B2 and $3F) +1]; inc(n,3); inc(i,4); until n >= Count; if n - Count >= 1 then Result[L]:= '='; if n - Count = 2 then Result[L-1]:= '='; end; end.
Si deseamos usar este código para trabajar con stream sin modificar la unit, podemos implementar estas dos funciones añadidas en otra unit:
function StreamEncode64(MStream: TMemoryStream): string; begin Result:= Encode64(MStream.Memory, MStream.Size); end; procedure StreamDencode64(var S: String; MStream: TMemoryStream); var Bytes: TAByte; begin Bytes:= Decode64(S); MStream.SetSize(Length(Bytes)); Move(Bytes[0], MStream.Memory^, Length(Bytes)); SetLength(Bytes,0); end;
Espero que sea de utilidad.
Saludos.