//Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package org.daxplore.presenter.server.servlets.png;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
/**
* {@code PngWriter} can write compressed PNG images from a {@link PixelSource}.
*
* <p>
* Note: this class generates PNGs directly, without the use of any third-party
* libraries, to avoid any external dependencies. Google App Engine applications
* are not allowed to use Java 2D, but the usage of a pure-Java image libary like
* Sanselan (http://incubator.apache.org/sanselan/site/index.html) might be
* preferable in a production application.
* </p>
*
* @author schwardo@google.com (Don Schwarz)
*/
public class PngWriter implements ImageWriter {
private static final byte[] HEADER = new byte[] { -119, 80, 78, 71, 13, 10, 26, 10, };
private static final byte[] IDAT = stringToBytes("IDAT");
private static final byte[] IEND = stringToBytes("IEND");
private static final byte[] IHDR = stringToBytes("IHDR");
//private static final int BYTES_PER_PIXEL = 3;
private static final String CONTENT_TYPE = "image/png";
private boolean usealpha;
public PngWriter() {
this(false);
}
public PngWriter(boolean usealpha) {
this.usealpha = usealpha;
}
@Override
public String getContentType() {
return CONTENT_TYPE;
}
@Override
public byte[] generateImage(PixelSource source) throws IOException {
int width = source.getWidth();
int height = source.getHeight();
CRC32 crc = new CRC32();
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(HEADER);
writeUnsignedInt(out, 13);
ByteArrayOutputStream header = new ByteArrayOutputStream();
header.write(IHDR);
writeUnsignedInt(header, width);
writeUnsignedInt(header, height);
header.write(8); // depth
if (usealpha) {
header.write(6); // direct model
} else {
header.write(2); // direct model
}
header.write(0); // compression
header.write(0); // no filter
header.write(0); // no interlace
crc.reset();
crc.update(header.toByteArray());
out.write(header.toByteArray());
writeUnsignedInt(out, crc.getValue());
Deflater deflator = new Deflater(5);
ByteArrayOutputStream zippedByteStream = new ByteArrayOutputStream(1024);
try(BufferedOutputStream zipStream = new BufferedOutputStream(new DeflaterOutputStream(zippedByteStream, deflator))) {
// optinally write to scanlines:
// byte[] scanLines = new byte[width * height * BYTES_PER_PIXEL + height];
for (int i = 0; i < width * height; i++) {
if (i % width == 0) {
zipStream.write(0); // filter
}
int pixel = source.getPixel(i % width, i / width);
if (usealpha) {
zipStream.write((pixel >> 24) & 0xff);
}
zipStream.write((pixel >> 16) & 0xff);
zipStream.write(pixel >> 8 & 0xff);
zipStream.write(pixel & 0xff);
}
}
byte[] zippedBytes = zippedByteStream.toByteArray();
writeUnsignedInt(out, zippedBytes.length);
out.write(IDAT);
crc.reset();
crc.update(IDAT);
out.write(zippedBytes);
crc.update(zippedBytes);
writeUnsignedInt(out, crc.getValue());
deflator.finish();
writeUnsignedInt(out, 0);
out.write(IEND);
crc.reset();
crc.update(IEND);
writeUnsignedInt(out, crc.getValue());
return out.toByteArray();
}
private static byte[] stringToBytes(String value) {
try {
return value.getBytes("US-ASCII");
} catch (UnsupportedEncodingException ex) {
// Should not happen.
throw new AssertionError(ex);
}
}
private static void writeUnsignedInt(OutputStream out, long value) throws IOException {
out.write((int) ((value >> 24) & 0xff));
out.write((int) ((value >> 16) & 0xff));
out.write((int) ((value >> 8) & 0xff));
out.write((int) (value & 0xff));
}
}