// 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 com.google.appengine.demos.mandelbrot;
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.
*
* @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";
public String getContentType() {
return CONTENT_TYPE;
}
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
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);
BufferedOutputStream zipStream = new BufferedOutputStream(
new DeflaterOutputStream(zippedByteStream, deflator));
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);
zipStream.write((pixel >> 16) & 0xff);
zipStream.write((pixel >> 8) & 0xff);
zipStream.write(pixel & 0xff);
}
zipStream.close();
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 RuntimeException(ex);
}
}
private 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));
}
}