//
// Base64Codec.java
//
/*
LOCI Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan,
Eric Kjellman and Brian Loranger.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.codec;
import loci.formats.FormatException;
/**
* Implements encoding (compress) and decoding (decompress) methods
* for Base64. This code was adapted from the Jakarta Commons Codec source,
* http://jakarta.apache.org/commons
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/Base64Codec.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/Base64Codec.java">SVN</a></dd></dl>
*
* @author Melissa Linkert linkert at wisc.edu
*/
public class Base64Codec extends BaseCodec implements Codec {
// Base64 alphabet and codes
private static final int FOURBYTE = 4;
private static final byte PAD = (byte) '=';
private static byte[] base64Alphabet = new byte[255];
private static byte[] lookupBase64Alphabet = new byte[255];
static {
for (int i=0; i<255; i++) {
base64Alphabet[i] = (byte) -1;
}
for (int i = 'Z'; i >= 'A'; i--) {
base64Alphabet[i] = (byte) (i - 'A');
}
for (int i = 'z'; i >= 'a'; i--) {
base64Alphabet[i] = (byte) (i - 'a' + 26);
}
for (int i = '9'; i >= '0'; i--) {
base64Alphabet[i] = (byte) (i - '0' + 52);
}
base64Alphabet['+'] = 62;
base64Alphabet['/'] = 63;
for (int i=0; i<=25; i++) {
lookupBase64Alphabet[i] = (byte) ('A' + i);
}
for (int i=26, j=0; i<=51; i++, j++) {
lookupBase64Alphabet[i] = (byte) ('a' + j);
}
for (int i=52, j=0; i<=61; i++, j++) {
lookupBase64Alphabet[i] = (byte) ('0' + j);
}
lookupBase64Alphabet[62] = (byte) '+';
lookupBase64Alphabet[63] = (byte) '/';
}
/**
* Encodes a block of data into Base64.
*
* @param input the data to be encoded.
* @param x ignored.
* @param y ignored.
* @param dims ignored.
* @param options ignored.
* @return The encoded data.
*/
public byte[] compress(byte[] input, int x, int y, int[] dims,
Object options) throws FormatException
{
int dataBits = input.length * 8;
int fewerThan24 = dataBits % 24;
int numTriples = dataBits / 24;
byte[] encoded = null;
int encodedLength = 0;
if (fewerThan24 != 0) encodedLength = (numTriples + 1) * 4;
else encodedLength = numTriples * 4;
encoded = new byte[encodedLength];
byte k, l, b1, b2, b3;
int encodedIndex = 0;
int dataIndex = 0;
for (int i=0; i<numTriples; i++) {
dataIndex = i * 3;
b1 = input[dataIndex];
b2 = input[dataIndex + 1];
b3 = input[dataIndex + 2];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte v1 = ((b1 & -128) == 0) ? (byte) (b1 >> 2) :
(byte) ((b1) >> 2 ^ 0xc0);
byte v2 = ((b2 & -128) == 0) ? (byte) (b2 >> 4) :
(byte) ((b2) >> 4 ^ 0xf0);
byte v3 = ((b3 & -128) == 0) ? (byte) (b3 >> 6) :
(byte) ((b3) >> 6 ^ 0xfc);
encoded[encodedIndex] = lookupBase64Alphabet[v1];
encoded[encodedIndex + 1] = lookupBase64Alphabet[v2 | (k << 4)];
encoded[encodedIndex + 2] = lookupBase64Alphabet[(l << 2) | v3];
encoded[encodedIndex + 3] = lookupBase64Alphabet[b3 & 0x3f];
encodedIndex += 4;
}
dataIndex = numTriples * 3;
if (fewerThan24 == 8) {
b1 = input[dataIndex];
k = (byte) (b1 & 0x03);
byte v = ((b1 & -128) == 0) ? (byte) (b1 >> 2) :
(byte) ((b1) >> 2 ^ 0xc0);
encoded[encodedIndex] = lookupBase64Alphabet[v];
encoded[encodedIndex + 1] = lookupBase64Alphabet[k << 4];
encoded[encodedIndex + 2] = (byte) '=';
encoded[encodedIndex + 3] = (byte) '=';
}
else if (fewerThan24 == 16) {
b1 = input[dataIndex];
b2 = input[dataIndex + 1];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte v1 = ((b1 & -128) == 0) ? (byte) (b1 >> 2) :
(byte) ((b1) >> 2 ^ 0xc0);
byte v2 = ((b2 & -128) == 0) ? (byte) (b2 >> 4) :
(byte) ((b2) >> 4 ^ 0xf0);
encoded[encodedIndex] = lookupBase64Alphabet[v1];
encoded[encodedIndex + 1] = lookupBase64Alphabet[v2 | (k << 4)];
encoded[encodedIndex + 2] = lookupBase64Alphabet[l << 2];
encoded[encodedIndex + 3] = (byte) '=';
}
return encoded;
}
/**
* Decompresses a block of data.
*
* @param base64Data the data to be decoded
* @return The decoded data
* @throws FormatException if data is not valid Base64 data
*/
public byte[] decompress(byte[] base64Data, Object options)
throws FormatException
{
// TODO: Add checks for invalid data.
if (base64Data.length == 0) return new byte[0];
int numberQuadruple = base64Data.length / FOURBYTE;
byte[] decodedData = null;
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
int encodedIndex = 0;
int dataIndex = 0;
int lastData = base64Data.length;
while (base64Data[lastData - 1] == PAD) {
if (--lastData == 0) {
return new byte[0];
}
}
decodedData = new byte[lastData - numberQuadruple];
for (int i=0; i<numberQuadruple; i++) {
dataIndex = i * 4;
marker0 = base64Data[dataIndex + 2];
marker1 = base64Data[dataIndex + 3];
b1 = base64Alphabet[base64Data[dataIndex]];
b2 = base64Alphabet[base64Data[dataIndex + 1]];
if (marker0 != PAD && marker1 != PAD) {
b3 = base64Alphabet[marker0];
b4 = base64Alphabet[marker1];
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex + 1] =
(byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
}
else if (marker0 == PAD) {
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
}
else if (marker1 == PAD) {
b3 = base64Alphabet[marker0];
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex + 1] =
(byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
}
encodedIndex += 3;
}
return decodedData;
}
/**
* Decodes a Base64 String by converting to bytes and passing to the
* decompress method.
*
* @param s the String to be decoded
* @return The decoded data
* @throws FormatException if s is not valid Base64 data.
*/
public byte[] base64Decode(String s) throws FormatException {
byte[] base64Data = s.getBytes();
return decompress(base64Data);
}
/**
* Main testing method. test is inherited from parent class.
*
* @param args ignored
* @throws FormatException Can only occur if there is a bug in the
* compress method.
*/
public static void main(String[] args) throws FormatException {
Base64Codec c = new Base64Codec();
c.test();
}
}