package org.richfaces.demo.mediaOutput;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.charset.Charset;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
public class MediaReader {
private static final int BUFFER_SIZE = 8192;
private boolean isIndexed = false;
private Charset asciiCharset = Charset.forName("US-ASCII");
private int sectionLength;
private int imageWidth;
private byte[] lengthBytes = new byte[4];
private byte[] chunkTypeBytes = new byte[4];
private Color[] colors;
private class Section {
protected void writeHeaderSectionData(OutputStream outChannel) throws IOException {
outChannel.write(lengthBytes);
outChannel.write(chunkTypeBytes);
}
protected void writeInt(OutputStream outChannel, int value) throws IOException {
byte[] bs = new byte[4];
ByteBuffer.wrap(bs).order(ByteOrder.BIG_ENDIAN).asIntBuffer().put(value);
outChannel.write(bs);
}
protected void writeSectionData(InputStream inChannel, OutputStream outChannel) throws IOException {
byte[] bs = new byte[BUFFER_SIZE];
int read = 0;
int remaining = sectionLength;
while ((read = inChannel.read(bs, 0, Math.min(remaining, bs.length))) > 0) {
outChannel.write(bs, 0, read);
remaining -= read;
}
if (remaining > 0) {
throw new IllegalArgumentException();
}
byte[] crc = new byte[4];
if (inChannel.read(crc) < crc.length) {
throw new IllegalArgumentException();
}
outChannel.write(crc);
}
public void write(InputStream inChannel, OutputStream outChannel) throws IOException {
writeHeaderSectionData(outChannel);
writeSectionData(inChannel, outChannel);
}
}
;
private class HeaderSection extends Section {
@Override
protected void writeSectionData(InputStream inChannel, OutputStream outChannel) throws IOException {
// // Width 4 bytes
// // Height 4 bytes
// // Bit depth 1 byte
// // Colour type 1 byte
// // Compression method 1 byte
// // Filter method 1 byte
// // Interlace method 1 byte
byte[] headerBytes = new byte[sectionLength];
if (inChannel.read(headerBytes) < headerBytes.length) {
throw new IllegalArgumentException();
}
if (headerBytes[8] != 8) {
throw new IllegalStateException("Color depth is not 8");
}
if (headerBytes[9] != 2 && headerBytes[9] != 3) {
throw new IllegalStateException("Unsupported color type");
}
imageWidth = ByteBuffer.wrap(headerBytes, 0, 4).order(ByteOrder.BIG_ENDIAN).asIntBuffer().get();
isIndexed = (headerBytes[9] == 3);
outChannel.write(headerBytes);
byte[] crc = new byte[4];
if (inChannel.read(crc) < crc.length) {
throw new IllegalArgumentException();
}
outChannel.write(crc);
}
}
;
private void transformColors(byte[] data, int offset, int length) {
float[] intensities = new float[3];
for (int i = offset; i < length + offset; i += 3) {
float weight = 0;
for (int j = 0; j < intensities.length; j++) {
intensities[j] = ((float) (data[i + j] & 0xFF)) / 255;
weight += intensities[j];
}
float r = 0;
float g = 0;
float b = 0;
for (int j = 0; j < intensities.length; j++) {
r += intensities[j] * colors[j].getRed();
g += intensities[j] * colors[j].getGreen();
b += intensities[j] * colors[j].getBlue();
}
data[i] = (byte) (r);
data[i + 1] = (byte) (g);
data[i + 2] = (byte) (b);
}
}
private class PaletteSection extends Section {
@Override
protected void writeSectionData(InputStream inChannel, OutputStream outChannel) throws IOException {
if (!isIndexed) {
super.writeSectionData(inChannel, outChannel);
} else {
byte[] data = new byte[2000 * 3];
int read;
int remaining = sectionLength;
assert (data.length < BUFFER_SIZE);
CRC32 crc32 = new CRC32();
crc32.update(chunkTypeBytes);
while ((read = (inChannel.read(data, 0, Math.min(remaining, data.length)))) > 0) {
remaining -= read;
transformColors(data, 0, read);
outChannel.write(data, 0, read);
crc32.update(data, 0, read);
}
if (remaining > 0) {
throw new IllegalArgumentException();
}
inChannel.skip(4);
writeInt(outChannel, (int) crc32.getValue());
}
}
}
;
private class LimitedInputStream extends FilterInputStream {
private int remaining;
protected LimitedInputStream(InputStream inChannel) {
super(inChannel);
this.remaining = sectionLength;
}
@Override
public int available() throws IOException {
return Math.min(super.available(), this.remaining);
}
@Override
public int read() throws IOException {
if (this.remaining > 0) {
int read = super.read();
if (read != -1) {
this.remaining--;
}
return read;
} else {
return -1;
}
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read = super.read(b, 0, Math.min(len, this.remaining));
if (read > 0) {
this.remaining -= read;
}
return read;
}
@Override
public synchronized void mark(int readlimit) {
throw new UnsupportedOperationException();
}
@Override
public boolean markSupported() {
return false;
}
@Override
public synchronized void reset() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long skip(long n) throws IOException {
long toSkip = Math.min(this.remaining, n);
long skipped = super.skip(toSkip);
if (skipped > 0) {
this.remaining -= skipped;
}
return skipped;
}
}
;
private class DataSection extends Section {
private byte paeth(byte a, byte b, byte c) {
int p = (a & 0xFF) + (b & 0xFF) - (c & 0xFF);
int pa = Math.abs(p - a);
int pb = Math.abs(p - b);
int pc = Math.abs(p - c);
int pr;
if (pa <= pb && pa <= pc) {
pr = a;
} else if (pb <= pc) {
pr = b;
} else {
pr = c;
}
return (byte) pr;
}
;
private class Filter {
protected int idx;
byte a = 0;
byte b = 0;
byte c = 0;
byte oa = 0;
byte ob = 0;
byte oc = 0;
int step = 3;
byte[] bs;
byte[] ps;
public void setIdx(int idx) {
this.idx = idx;
oa = a;
ob = b;
oc = c;
b = ps[idx];
if (idx > step) {
a = bs[idx - step];
c = ps[idx - step];
}
}
public void next() {
idx = idx + step;
setIdx(idx);
}
}
;
private class SubFilter extends Filter {
@Override
public void next() {
bs[idx] = (byte) ((bs[idx] & 0xFF) + (a & 0xFF) - (oa & 0xFF));
setIdx(idx + step);
}
}
;
private class UpFilter extends Filter {
@Override
public void next() {
bs[idx] = (byte) ((bs[idx] & 0xFF) + (b & 0xFF) - (ob & 0xFF));
setIdx(idx + step);
}
}
;
private class PaethFilter extends Filter {
@Override
public void next() {
bs[idx] = (byte) (paeth(a, b, c) - paeth(oa, ob, oc) + (bs[idx] & 0xFF));
setIdx(idx + step);
}
}
private void reconstruct(byte[] bs, byte[] ps) {
Filter[] filters = new Filter[3];
for (int i = 0; i < filters.length; i++) {
switch (bs[0]) {
case 0:
filters[i] = new Filter();
break;
case 1:
filters[i] = new SubFilter();
break;
case 2:
filters[i] = new UpFilter();
break;
case 4:
filters[i] = new PaethFilter();
break;
default:
throw new IllegalArgumentException(Integer.toHexString(bs[0]));
}
filters[i].bs = bs;
filters[i].ps = ps;
filters[i].step = 3;
filters[i].setIdx(1 + i);
}
for (int i = 1; i < (bs.length - 1) / 3; i++) {
for (Filter filter : filters) {
filter.next();
}
}
bs[0] = 0;
}
@Override
public void write(InputStream inChannel, OutputStream outChannel) throws IOException {
if (isIndexed) {
super.write(inChannel, outChannel);
} else {
byte[] ps = new byte[imageWidth * 3 + 1];
byte[] bs = new byte[imageWidth * 3 + 1];
assert (bs.length < BUFFER_SIZE);
int read = 0;
InputStream inflaterInputStream = new BufferedInputStream(new InflaterInputStream(new LimitedInputStream(
inChannel), new Inflater(), 2048), BUFFER_SIZE);
ByteArrayOutputStream baos = new ByteArrayOutputStream(sectionLength);
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(baos, new Deflater(), 2048);
while ((read = (inflaterInputStream.read(bs))) > 0) {
reconstruct(bs, ps);
transformColors(bs, 1, read - 1);
deflaterOutputStream.write(bs, 0, read);
byte[] swapVar = bs;
bs = ps;
ps = swapVar;
}
deflaterOutputStream.finish();
byte[] compressedSectionBytes = baos.toByteArray();
writeInt(outChannel, compressedSectionBytes.length);
CRC32 crc32 = new CRC32();
outChannel.write(chunkTypeBytes);
crc32.update(chunkTypeBytes);
if (inChannel.skip(4) < 4) {
throw new IllegalArgumentException();
}
outChannel.write(compressedSectionBytes);
writeInt(outChannel, (int) crc32.getValue());
}
}
}
;
private Section readNextSection(InputStream inChannel) throws IOException {
int read = inChannel.read(lengthBytes);
if (read != -1) {
if (read < lengthBytes.length) {
throw new IllegalArgumentException();
}
if (inChannel.read(chunkTypeBytes) < chunkTypeBytes.length) {
throw new IllegalArgumentException();
}
IntBuffer lengthBuffer = ByteBuffer.wrap(lengthBytes).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
sectionLength = lengthBuffer.get(0);
String chunkTypeString = new String(chunkTypeBytes, asciiCharset);
if ("IHDR".equals(chunkTypeString)) {
return new HeaderSection();
} else if ("PLTE".equals(chunkTypeString)) {
return new PaletteSection();
} else if ("IDAT".equals(chunkTypeString)) {
return new DataSection();
} else {
return new Section();
}
} else {
return null;
}
}
public void write(Color[] colors, OutputStream os, InputStream is) throws IOException {
this.colors = colors;
Section section = null;
while ((section = readNextSection(is)) != null) {
section.write(is, os);
}
}
}