Last active
September 30, 2016 19:54
-
-
Save finebyte/94a7bd445691926eb99c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package uk.co.finebyte.pebbleglance; | |
import android.graphics.*; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.zip.CRC32; | |
import java.util.zip.Deflater; | |
import java.util.zip.DeflaterOutputStream; | |
/** | |
* PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file. | |
* The Image is presumed to use the DirectColorModel. | |
* | |
* <p>Thanks to Jay Denny at KeyPoint Software | |
* http://www.keypoint.com/ | |
* who let me develop this code on company time.</p> | |
* | |
* <p>You may contact me with (probably very-much-needed) improvements, | |
* comments, and bug fixes at:</p> | |
* | |
* <p><code>david@catcode.com</code></p> | |
* | |
* <p>This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2.1 of the License, or (at your option) any later version.</p> | |
* | |
* <p>This library 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 | |
* Lesser General Public License for more details.</p> | |
* | |
* <p>You should have received a copy of the GNU Lesser General Public | |
* License along with this library; if not, write to the Free Software | |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
* A copy of the GNU LGPL may be found at | |
* <code>http://www.gnu.org/copyleft/lesser.html</code></p> | |
* | |
* @author J. David Eisenberg | |
* @version 1.5, 19 Oct 2003 | |
* | |
* CHANGES: | |
* -------- | |
* 19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object Refinery Limited); | |
* 19-Sep-2003 : Fix for platforms using EBCDIC (contributed by Paulo Soares); | |
* 19-Oct-2003 : Change private fields to protected fields so that | |
* PngEncoderB can inherit them (JDE) | |
* Fixed bug with calculation of nRows | |
*/ | |
public class PngEncoder extends Object { | |
String tag="PngEncoder"; | |
/** Constant specifying that alpha channel should be encoded. */ | |
public static final boolean ENCODE_ALPHA = true; | |
/** Constant specifying that alpha channel should not be encoded. */ | |
public static final boolean NO_ALPHA = false; | |
/** Constants for filter (NONE) */ | |
public static final int FILTER_NONE = 0; | |
/** Constants for filter (SUB) */ | |
public static final int FILTER_SUB = 1; | |
/** Constants for filter (UP) */ | |
public static final int FILTER_UP = 2; | |
/** Constants for filter (LAST) */ | |
public static final int FILTER_LAST = 2; | |
/** IHDR tag. */ | |
protected static final byte IHDR[] = {73, 72, 68, 82}; | |
/** IDAT tag. */ | |
protected static final byte IDAT[] = {73, 68, 65, 84}; | |
/** IEND tag. */ | |
protected static final byte IEND[] = {73, 69, 78, 68}; | |
/** The png bytes. */ | |
protected byte[] pngBytes; | |
/** The prior row. */ | |
protected byte[] priorRow; | |
/** The left bytes. */ | |
protected byte[] leftBytes; | |
/** The image. */ | |
protected Bitmap image; | |
/** The width. */ | |
protected int width, height; | |
/** The byte position. */ | |
protected int bytePos, maxPos; | |
/** CRC. */ | |
protected CRC32 crc = new CRC32(); | |
/** The CRC value. */ | |
protected long crcValue; | |
/** Encode alpha? */ | |
protected boolean encodeAlpha; | |
/** The filter type. */ | |
protected int filter; | |
/** The bytes-per-pixel. */ | |
protected int bytesPerPixel; | |
/** The compression level. */ | |
protected int compressionLevel; | |
private int depth=8; | |
private HashMap<Integer,Integer> palette_map = new HashMap<Integer, Integer>(); | |
/** | |
* Class constructor | |
*/ | |
public PngEncoder() { | |
this(null, false, FILTER_NONE, 0); | |
} | |
/** | |
* Class constructor specifying Image to encode, with no alpha channel encoding. | |
* | |
* @param image A Java Image object which uses the DirectColorModel | |
* @see java.awt.Image | |
*/ | |
public PngEncoder(Bitmap image) { | |
this(image, false, FILTER_NONE, 0); | |
} | |
/** | |
* Class constructor specifying Image to encode, and whether to encode alpha. | |
* | |
* @param image A Java Image object which uses the DirectColorModel | |
* @param encodeAlpha Encode the alpha channel? false=no; true=yes | |
* @see java.awt.Image | |
*/ | |
public PngEncoder(Bitmap image, boolean encodeAlpha) { | |
this(image, encodeAlpha, FILTER_NONE, 0); | |
} | |
/** | |
* Class constructor specifying Image to encode, whether to encode alpha, and filter to use. | |
* | |
* @param image A Java Image object which uses the DirectColorModel | |
* @param encodeAlpha Encode the alpha channel? false=no; true=yes | |
* @param whichFilter 0=none, 1=sub, 2=up | |
* @see java.awt.Image | |
*/ | |
public PngEncoder(Bitmap image, boolean encodeAlpha, int whichFilter) { | |
this(image, encodeAlpha, whichFilter, 0); | |
} | |
/** | |
* Class constructor specifying Image source to encode, whether to encode alpha, filter to use, | |
* and compression level. | |
* | |
* @param image A Java Image object | |
* @param encodeAlpha Encode the alpha channel? false=no; true=yes | |
* @param whichFilter 0=none, 1=sub, 2=up | |
* @param compLevel 0..9 | |
* @see java.awt.Image | |
*/ | |
public PngEncoder(Bitmap image, boolean encodeAlpha, int whichFilter, int compLevel) { | |
this.image = image; | |
this.encodeAlpha = encodeAlpha; | |
setFilter(whichFilter); | |
if (compLevel >= 0 && compLevel <= 9) { | |
this.compressionLevel = compLevel; | |
} | |
} | |
public void setDepth(int depth) { | |
this.depth=depth; | |
} | |
/** | |
* Set the image to be encoded | |
* | |
* @param image A Java Image object which uses the DirectColorModel | |
* @see java.awt.Image | |
* @see java.awt.image.DirectColorModel | |
*/ | |
public void setImage(Bitmap image) { | |
this.image = image; | |
pngBytes = null; | |
} | |
/** | |
* Creates an array of bytes that is the PNG equivalent of the current image, specifying | |
* whether to encode alpha or not. | |
* | |
* @param encodeAlpha boolean false=no alpha, true=encode alpha | |
* @return an array of bytes, or null if there was a problem | |
*/ | |
public byte[] pngEncode(boolean encodeAlpha) { | |
byte[] pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10}; | |
if (image == null) { | |
return null; | |
} | |
width = image.getWidth(); | |
height = image.getHeight(); | |
/* | |
* start with an array that is big enough to hold all the pixels | |
* (plus filter bytes), and an extra 200 bytes for header info | |
*/ | |
pngBytes = new byte[((width + 1) * height * 3) + 200]; | |
/* | |
* keep track of largest byte written to the array | |
*/ | |
maxPos = 0; | |
bytePos = writeBytes(pngIdBytes, 0); | |
//hdrPos = bytePos; | |
writeHeader(); | |
// Compress image data | |
// if this is a 4bit / 16 colour image it also creates the relvant palette | |
byte[] compressedimage = compressImageData(); | |
// Write the Palette | |
bytePos = writeBytes(createPebblePalette(), bytePos); | |
//dataPos = bytePos; | |
writeImageData(compressedimage); | |
writeEnd(); | |
pngBytes = resizeByteArray(pngBytes, maxPos); | |
return pngBytes; | |
} | |
/** | |
* Creates an array of bytes that is the PNG equivalent of the current image. | |
* Alpha encoding is determined by its setting in the constructor. | |
* | |
* @return an array of bytes, or null if there was a problem | |
*/ | |
public byte[] pngEncode() { | |
return pngEncode(encodeAlpha); | |
} | |
/** | |
* Set the alpha encoding on or off. | |
* | |
* @param encodeAlpha false=no, true=yes | |
*/ | |
public void setEncodeAlpha(boolean encodeAlpha) { | |
this.encodeAlpha = encodeAlpha; | |
} | |
/** | |
* Retrieve alpha encoding status. | |
* | |
* @return boolean false=no, true=yes | |
*/ | |
public boolean getEncodeAlpha() { | |
return encodeAlpha; | |
} | |
/** | |
* Set the filter to use | |
* | |
* @param whichFilter from constant list | |
*/ | |
public void setFilter(int whichFilter) { | |
this.filter = FILTER_NONE; | |
if (whichFilter <= FILTER_LAST) { | |
this.filter = whichFilter; | |
} | |
} | |
/** | |
* Retrieve filtering scheme | |
* | |
* @return int (see constant list) | |
*/ | |
public int getFilter() { | |
return filter; | |
} | |
/** | |
* Set the compression level to use | |
* | |
* @param level 0 through 9 | |
*/ | |
public void setCompressionLevel(int level) { | |
if (level >= 0 && level <= 9) { | |
this.compressionLevel = level; | |
} | |
} | |
/** | |
* Retrieve compression level | |
* | |
* @return int in range 0-9 | |
*/ | |
public int getCompressionLevel() { | |
return compressionLevel; | |
} | |
/** | |
* Increase or decrease the length of a byte array. | |
* | |
* @param array The original array. | |
* @param newLength The length you wish the new array to have. | |
* @return Array of newly desired length. If shorter than the | |
* original, the trailing elements are truncated. | |
*/ | |
protected byte[] resizeByteArray(byte[] array, int newLength) { | |
byte[] newArray = new byte[newLength]; | |
int oldLength = array.length; | |
System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength)); | |
return newArray; | |
} | |
/** | |
* Write an array of bytes into the pngBytes array. | |
* Note: This routine has the side effect of updating | |
* maxPos, the largest element written in the array. | |
* The array is resized by 1000 bytes or the length | |
* of the data to be written, whichever is larger. | |
* | |
* @param data The data to be written into pngBytes. | |
* @param offset The starting point to write to. | |
* @return The next place to be written to in the pngBytes array. | |
*/ | |
protected int writeBytes(byte[] data, int offset) { | |
maxPos = Math.max(maxPos, offset + data.length); | |
if (data.length + offset > pngBytes.length) { | |
pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length)); | |
} | |
System.arraycopy(data, 0, pngBytes, offset, data.length); | |
return offset + data.length; | |
} | |
/** | |
* Write an array of bytes into the pngBytes array, specifying number of bytes to write. | |
* Note: This routine has the side effect of updating | |
* maxPos, the largest element written in the array. | |
* The array is resized by 1000 bytes or the length | |
* of the data to be written, whichever is larger. | |
* | |
* @param data The data to be written into pngBytes. | |
* @param nBytes The number of bytes to be written. | |
* @param offset The starting point to write to. | |
* @return The next place to be written to in the pngBytes array. | |
*/ | |
protected int writeBytes(byte[] data, int nBytes, int offset) { | |
maxPos = Math.max(maxPos, offset + nBytes); | |
if (nBytes + offset > pngBytes.length) { | |
pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nBytes)); | |
} | |
System.arraycopy(data, 0, pngBytes, offset, nBytes); | |
return offset + nBytes; | |
} | |
/** | |
* Write a two-byte integer into the pngBytes array at a given position. | |
* | |
* @param n The integer to be written into pngBytes. | |
* @param offset The starting point to write to. | |
* @return The next place to be written to in the pngBytes array. | |
*/ | |
protected int writeInt2(int n, int offset) { | |
byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}; | |
return writeBytes(temp, offset); | |
} | |
/** | |
* Write a four-byte integer into the pngBytes array at a given position. | |
* | |
* @param n The integer to be written into pngBytes. | |
* @param offset The starting point to write to. | |
* @return The next place to be written to in the pngBytes array. | |
*/ | |
protected int writeInt4(int n, int offset) { | |
byte[] temp = {(byte) ((n >> 24) & 0xff), | |
(byte) ((n >> 16) & 0xff), | |
(byte) ((n >> 8) & 0xff), | |
(byte) (n & 0xff)}; | |
return writeBytes(temp, offset); | |
} | |
/** | |
* Write a single byte into the pngBytes array at a given position. | |
* | |
* @param b The integer to be written into pngBytes. | |
* @param offset The starting point to write to. | |
* @return The next place to be written to in the pngBytes array. | |
*/ | |
protected int writeByte(int b, int offset) { | |
byte[] temp = {(byte) b}; | |
return writeBytes(temp, offset); | |
} | |
/** | |
* Write a PNG "IHDR" chunk into the pngBytes array. | |
*/ | |
protected void writeHeader() { | |
int startPos; | |
startPos = bytePos = writeInt4(13, bytePos); | |
bytePos = writeBytes(IHDR, bytePos); | |
width = image.getWidth(); | |
height = image.getHeight(); | |
bytePos = writeInt4(width, bytePos); | |
bytePos = writeInt4(height, bytePos); | |
bytePos = writeByte(depth, bytePos); // bit depth | |
// bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model | |
bytePos = writeByte(3, bytePos); // Palette | |
bytePos = writeByte(0, bytePos); // compression method | |
bytePos = writeByte(0, bytePos); // filter method | |
bytePos = writeByte(0, bytePos); // no interlace | |
crc.reset(); | |
crc.update(pngBytes, startPos, bytePos - startPos); | |
crcValue = crc.getValue(); | |
bytePos = writeInt4((int) crcValue, bytePos); | |
} | |
/** | |
* Perform "sub" filtering on the given row. | |
* Uses temporary array leftBytes to store the original values | |
* of the previous pixels. The array is 16 bytes long, which | |
* will easily hold two-byte samples plus two-byte alpha. | |
* | |
* @param pixels The array holding the scan lines being built | |
* @param startPos Starting position within pixels of bytes to be filtered. | |
* @param width Width of a scanline in pixels. | |
*/ | |
protected void filterSub(byte[] pixels, int startPos, int width) { | |
int i; | |
int offset = bytesPerPixel; | |
int actualStart = startPos + offset; | |
int nBytes = width * bytesPerPixel; | |
int leftInsert = offset; | |
int leftExtract = 0; | |
for (i = actualStart; i < startPos + nBytes; i++) { | |
leftBytes[leftInsert] = pixels[i]; | |
pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256); | |
leftInsert = (leftInsert + 1) % 0x0f; | |
leftExtract = (leftExtract + 1) % 0x0f; | |
} | |
} | |
/** | |
* Perform "up" filtering on the given row. | |
* Side effect: refills the prior row with current row | |
* | |
* @param pixels The array holding the scan lines being built | |
* @param startPos Starting position within pixels of bytes to be filtered. | |
* @param width Width of a scanline in pixels. | |
*/ | |
protected void filterUp(byte[] pixels, int startPos, int width) { | |
int i, nBytes; | |
byte currentByte; | |
nBytes = width * bytesPerPixel; | |
for (i = 0; i < nBytes; i++) { | |
currentByte = pixels[startPos + i]; | |
pixels[startPos + i] = (byte) ((pixels[startPos + i] - priorRow[i]) % 256); | |
priorRow[i] = currentByte; | |
} | |
} | |
/** | |
* Write the image data into the pngBytes array. | |
* This will write one or more PNG "IDAT" chunks. In order | |
* to conserve memory, this method grabs as many rows as will | |
* fit into 32K bytes, or the whole image; whichever is less. | |
* | |
* | |
* @return the compress image data or 0 length array on error | |
*/ | |
protected byte[] compressImageData() { | |
int rowsLeft = height; // number of rows remaining to write | |
int startRow = 0; // starting row to process this time through | |
int nRows; // how many rows to grab at a time | |
byte[] scanLines; // the scan lines to be compressed | |
int scanPos; // where we are in the scan lines | |
int startPos; // where this line's actual pixels start (used for filtering) | |
byte[] compressedLines; // the resultant compressed lines | |
int nCompressed; // how big is the compressed area? | |
bytesPerPixel = 1;// (encodeAlpha) ? 4 : 3; | |
Deflater scrunch = new Deflater(compressionLevel); | |
ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024); | |
DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch); | |
try { | |
while (rowsLeft > 0) { | |
// Pebble | |
// Assume the image is small (i.e. has been resized before sending to no more that 168) | |
nRows = height; | |
int[] pixels = new int[width * nRows]; | |
image.getPixels(pixels, 0, width, 0, startRow, width, nRows); | |
if (depth==8) { | |
scanLines = new byte[width * nRows * bytesPerPixel + nRows]; | |
} else { | |
int pixels_per_byte = 8 / depth; | |
int scanLineLength = (width + (pixels_per_byte-width%pixels_per_byte))/pixels_per_byte; | |
Log.d(tag,"new scanLineLength=" + scanLineLength); | |
scanLines = new byte[scanLineLength * nRows * bytesPerPixel + nRows]; | |
} | |
int palette_count=0; | |
int max = 8 /depth; | |
int count = 1; | |
byte output=0x00; | |
int max_cols= (int) Math.pow(2,depth); | |
scanPos = 0; | |
for (int i = 0; i < width * nRows; i++) { | |
if (i % width == 0) { | |
scanLines[scanPos++] = (byte) filter; | |
} | |
if (depth==8) { | |
// For 6 bit (64 Colours), get 2 bit value for each of ARGB | |
// in Pebble dp1-4 Alpha is the just the top bit | |
byte pix = (byte) 0x00; | |
// Alpha isn't working yet | |
// pix = (byte) (pix | ((byte)(Color.alpha(pixels[i])/64))<<6); | |
pix = (byte) (pix | ((byte)(Color.red(pixels[i])/64))<<4); | |
pix = (byte) (pix | ((byte)(Color.green(pixels[i])/64))<<2); | |
pix = (byte) (pix | ((byte)(Color.blue(pixels[i])/64))); | |
scanLines[scanPos++]=pix; | |
} else { | |
// For 4 bit (16 Colours) from Palette | |
// Each pixel is mapped to the relevant palette entry | |
// Assumes we already have a 16 colour image | |
// Create the palette as we go | |
Integer pvalue = 0; | |
if (depth==1) { | |
if (pixels[i] == Color.BLACK) { | |
pvalue = 0; | |
} else { | |
pvalue = 1; | |
} | |
} else { | |
pvalue= palette_map.get(pixels[i]); | |
if (pvalue == null) { | |
pvalue = palette_count; | |
palette_map.put(pixels[i], palette_count); | |
palette_count++; | |
} | |
// Warn if the palette is too big | |
if (palette_count >= max_cols) { | |
Log.d(tag, "Bad colour image - more than " + max_cols + " colours! (" + palette_count + ")"); | |
pvalue = 0; | |
} | |
} | |
int shift = 8 - (count * depth); | |
byte pix = (byte)pvalue.intValue(); | |
output = (byte) (output | (pix<<shift)); | |
if (count==max) { | |
scanLines[scanPos]=output; | |
scanPos++; | |
output=0x00; | |
count=1; | |
} | |
else { | |
if ((i!=0) && ((i+1)%width==0)) { | |
scanLines[scanPos]=output; | |
scanPos++; | |
output=0x00; | |
count=1; | |
} else { | |
count++; | |
} | |
} | |
} | |
} | |
/* | |
* Write these lines to the output area | |
*/ | |
compBytes.write(scanLines, 0, scanPos); | |
startRow += nRows; | |
rowsLeft -= nRows; | |
} | |
compBytes.close(); | |
scrunch.finish(); | |
compressedLines = outBytes.toByteArray(); | |
return compressedLines; | |
} | |
catch (IOException e) { | |
System.err.println(e.toString()); | |
return new byte[0]; | |
} | |
} | |
private void writeImageData(byte[] compressedLines) { | |
/* | |
* Write the compressed bytes | |
*/ | |
int nCompressed = compressedLines.length; | |
crc.reset(); | |
bytePos = writeInt4(nCompressed, bytePos); | |
bytePos = writeBytes(IDAT, bytePos); | |
crc.update(IDAT); | |
bytePos = writeBytes(compressedLines, nCompressed, bytePos); | |
crc.update(compressedLines, 0, nCompressed); | |
crcValue = crc.getValue(); | |
bytePos = writeInt4((int) crcValue, bytePos); | |
} | |
/** | |
* Write a PNG "IEND" chunk into the pngBytes array. | |
*/ | |
protected void writeEnd() { | |
bytePos = writeInt4(0, bytePos); | |
bytePos = writeBytes(IEND, bytePos); | |
crc.reset(); | |
crc.update(IEND); | |
crcValue = crc.getValue(); | |
bytePos = writeInt4((int) crcValue, bytePos); | |
} | |
private byte[] createPebblePalette() { | |
// PLTE = len + header + data + crc | |
int cols=64; | |
int max_cols= (int) Math.pow(2,depth); | |
if (depth!=8) { | |
cols=max_cols; | |
} | |
int data_len=cols*3; | |
int len=4+4+data_len+4; | |
byte[] pal = new byte[len]; | |
// Add the length | |
byte[] lenb=intToInt4Bytes(data_len); | |
pal[0]=lenb[0]; | |
pal[1]=lenb[1]; | |
pal[2]=lenb[2]; | |
pal[3]=lenb[3]; | |
// Add the chunk type | |
pal[4]='P'; | |
pal[5]='L'; | |
pal[6]='T'; | |
pal[7]='E'; | |
// Create the palette and add | |
if ((depth==2)||(depth==4)) { | |
int pos=8; | |
// Here we assume the palette_map has been updated already which is yukky | |
Log.d(tag,"Pebble Pallete was depth "+depth+", size " + palette_map.size()); | |
Iterator<Integer> it = palette_map.keySet().iterator(); | |
StringBuffer strbuf = new StringBuffer(); | |
while (it.hasNext()) { | |
int c = it.next(); | |
int i = palette_map.get(c); | |
Log.d(tag,"PLTE c=" + c + " i="+i); | |
if (i<max_cols) { | |
i=i*3; | |
pal[pos+i]=(byte)ConvertTo6Bit(Color.red(c)); | |
pal[pos+i+1]=(byte)ConvertTo6Bit(Color.green(c)); | |
pal[pos+i+2]=(byte)ConvertTo6Bit(Color.blue(c)); | |
} else { | |
Log.d(tag,"Skipping pallette item > 16"); | |
} | |
} | |
} else if (depth==1){ | |
int pos=8; | |
pal[pos]=(byte)Color.red(Color.BLACK); | |
pal[pos+1]=(byte)Color.green(Color.BLACK); | |
pal[pos+2]=(byte)Color.blue(Color.BLACK); | |
pal[pos+3]=(byte)Color.red(Color.WHITE); | |
pal[pos+4]=(byte)Color.green(Color.WHITE); | |
pal[pos+5]=(byte)Color.blue(Color.WHITE); | |
} else if (depth==8){ | |
int pos=8; | |
byte[] colours = {0, 85, (byte) 170, (byte) 255}; | |
for (byte r: colours) { | |
for (byte g:colours) { | |
for (byte b:colours) { | |
pal[pos++]=r; | |
pal[pos++]=g; | |
pal[pos++]=b; | |
} | |
} | |
} | |
} | |
// Calc CRC and add | |
CRC32 palcrc = new CRC32(); | |
palcrc.update(pal, 4, 4+data_len); | |
long palcrcl = palcrc.getValue(); | |
//7801010b | |
byte[] palcrcb = intToInt4Bytes((int)palcrcl); | |
pal[4+4+data_len+0]=palcrcb[0]; | |
pal[4+4+data_len+1]=palcrcb[1]; | |
pal[4+4+data_len+2]=palcrcb[2]; | |
pal[4+4+data_len+3]=palcrcb[3]; | |
return pal; | |
} | |
private byte[] intToInt4Bytes(int n) { | |
byte[] temp = {(byte) ((n >> 24) & 0xff), | |
(byte) ((n >> 16) & 0xff), | |
(byte) ((n >> 8) & 0xff), | |
(byte) (n & 0xff)}; | |
return temp; | |
} | |
private static int ConvertTo6Bit(int value) { | |
if (value < 43) return 0; | |
if (value < 129) return 85; | |
if (value < 213) return 170; | |
else return 255; | |
} | |
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); | |
public static String bytesToHex(byte[] bytes) { | |
char[] hexChars = new char[bytes.length * 2]; | |
for ( int j = 0; j < bytes.length; j++ ) { | |
int v = bytes[j] & 0xFF; | |
hexChars[j * 2] = hexArray[v >>> 4]; | |
hexChars[j * 2 + 1] = hexArray[v & 0x0F]; | |
} | |
return new String(hexChars); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.*; | |
import android.os.*; | |
import android.graphics.*; | |
import com.jabistudio.androidjhlabs.filter.DiffusionFilter; | |
import com.jabistudio.androidjhlabs.filter.GrayscaleFilter; | |
import com.jabistudio.androidjhlabs.filter.QuantizeFilter; | |
import com.jabistudio.androidjhlabs.filter.ScaleFilter; | |
import com.jabistudio.androidjhlabs.filter.util.AndroidUtils; | |
import junit.framework.Test; | |
public class TestPngEncoder{ | |
public static String tag="TestPngEncoder"; | |
public TestPngEncoder() { | |
} | |
public static int calculateInSampleSize( | |
BitmapFactory.Options options, int reqWidth, int reqHeight) { | |
// Raw height and width of image | |
final int height = options.outHeight; | |
final int width = options.outWidth; | |
int inSampleSize = 1; | |
if (height > reqHeight || width > reqWidth) { | |
final int halfHeight = height / 2; | |
final int halfWidth = width / 2; | |
// Calculate the largest inSampleSize value that is a power of 2 and keeps both | |
// height and width larger than the requested height and width. | |
while ((halfHeight / inSampleSize) > reqHeight | |
&& (halfWidth / inSampleSize) > reqWidth) { | |
inSampleSize *= 2; | |
} | |
} | |
return inSampleSize; | |
} | |
public void doEncode(String in_filename, String out_filename, int depth, boolean dither) { | |
File path = Environment.getExternalStoragePublicDirectory( | |
Environment.DIRECTORY_DOWNLOADS); | |
Log.d(tag,"Loading image to be converted"); | |
String infile= path.getAbsolutePath()+File.separator+in_filename; | |
// Load the bitmap with sampling to reduce the size in memory | |
// See http://developer.android.com/training/displaying-bitmaps/load-bitmap.html | |
// First decode with inJustDecodeBounds=true to check dimensions | |
final BitmapFactory.Options options = new BitmapFactory.Options(); | |
options.inJustDecodeBounds = true; | |
BitmapFactory.decodeFile(infile, options); | |
// Calculate inSampleSize | |
// Where 100,100 is the target size (but will NOT be the actual image size) | |
options.inSampleSize = calculateInSampleSize(options, 100, 100); | |
// Decode bitmap with inSampleSize set | |
options.inJustDecodeBounds = false; | |
Bitmap b= BitmapFactory.decodeFile(infile, options); | |
Log.d(tag,"Loading Running filters"); | |
// Get the bitmap is int array | |
int[] ba=AndroidUtils.bitmapToIntArray(b); | |
int width=144; | |
int height=168; | |
// Scale bitmap to real target size e.g. 144,168 | |
ScaleFilter sf = new ScaleFilter(width,height); | |
int[] sa=sf.filter(ba,b.getWidth(),b.getHeight()); | |
int baf[]=null; | |
int num_cols=64; | |
if (depth==8) { | |
QuantizeFilter qf = new QuantizeFilter(); | |
qf.setNumColors(num_cols); | |
qf.setDither(dither); | |
baf = qf.filter(sa, width, height); | |
} | |
if (depth==4) { | |
num_cols = 16; | |
QuantizeFilter qf = new QuantizeFilter(); | |
qf.setNumColors(num_cols); | |
qf.setDither(dither); | |
baf = qf.filter(sa, width, height); | |
} | |
if (depth==2) { | |
GrayscaleFilter gf = new GrayscaleFilter(); | |
int[] gsf = gf.filter(sa,width,height); | |
DiffusionFilter df = new DiffusionFilter(); | |
df.setColorDither(true); | |
df.setLevels(3); | |
baf = df.filter(gsf, width, height); | |
} | |
if (depth==1) { | |
DiffusionFilter df = new DiffusionFilter(); | |
df.setColorDither(false); | |
df.setLevels(2); | |
baf = df.filter(sa, width, height); | |
} | |
Bitmap newBitmap = Bitmap.createBitmap(baf, width, height, Bitmap.Config.ARGB_8888); | |
// Encode the Bitmap , no alpha (not working yet), no png filter, max compression | |
// Note this is sllllooowwwwww | |
Log.d(tag,"Encoding"); | |
PngEncoder p = new PngEncoder(newBitmap,false,0,9); | |
p.setDepth(depth); | |
byte[] o = p.pngEncode(); | |
Log.d(tag,"Encoding done"); | |
// Save final output to file if necessary | |
try { | |
FileOutputStream out = new FileOutputStream(path.getAbsolutePath() + File.separator + out_filename); | |
out.write(o, 0, o.length); | |
out.close(); | |
Log.d(tag, "File write done"); | |
} catch (IOException ex) { | |
Log.d(tag,"Unable to save new png " + ex); | |
} | |
} | |
public static void main(String[] args) { | |
TestPngEncoder tpe = new TestPngEncoder(); | |
// Create a B&W dithered image | |
tpe.doEncode("test.png","out.png",1,true); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment