package org.jcodec.codecs.png; import java.nio.ByteBuffer; import java.util.zip.CRC32; import java.util.zip.Deflater; import org.jcodec.common.VideoEncoder; import org.jcodec.common.io.NIOUtils; import org.jcodec.common.model.ColorSpace; import org.jcodec.common.model.Picture8Bit; import org.jcodec.common.tools.MainUtils; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Simplistic PNG encoder, doesn't support anything. * * @author Stanislav Vitvitskyy * */ public class PNGEncoder extends VideoEncoder { private static final long PNGSIG = 0x89504e470d0a1a0aL; private static final int TAG_IHDR = 0x49484452; private static final int TAG_IDAT = 0x49444154; private static final int TAG_IEND = 0x49454e44; private static class IHDR { private int width; private int height; private byte bitDepth; private byte colorType; private byte compressionType; private byte filterType; private byte interlaceType; public void write(ByteBuffer data) { data.putInt(width); data.putInt(height); data.put(bitDepth); data.put(colorType); data.put(compressionType); data.put(filterType); data.put(interlaceType); } } private static int crc32(ByteBuffer from, ByteBuffer to) { from.limit(to.position()); CRC32 crc32 = new CRC32(); crc32.update(NIOUtils.toArray(from)); return (int) crc32.getValue(); } private static final int PNG_COLOR_MASK_COLOR = 2; @Override public EncodedFrame encodeFrame8Bit(Picture8Bit pic, ByteBuffer out) { ByteBuffer _out = out.duplicate(); _out.putLong(PNGSIG); IHDR ihdr = new IHDR(); ihdr.width = pic.getWidth(); ihdr.height = pic.getHeight(); ihdr.bitDepth = 8; ihdr.colorType = PNG_COLOR_MASK_COLOR; _out.putInt(13); ByteBuffer crcFrom = _out.duplicate(); _out.putInt(TAG_IHDR); ihdr.write(_out); _out.putInt(crc32(crcFrom, _out)); Deflater deflater = new Deflater(); byte[] rowData = new byte[pic.getWidth() * 3 + 1]; byte[] pix = pic.getPlaneData(0); byte[] buffer = new byte[1 << 15]; int ptr = 0, len = buffer.length; // We do one extra iteration here to flush the deflator for (int row = 0, bptr = 0; row < pic.getHeight() + 1; row++) { int count; while ((count = deflater.deflate(buffer, ptr, len)) > 0) { ptr += count; len -= count; if (len == 0) { _out.putInt(ptr); crcFrom = _out.duplicate(); _out.putInt(TAG_IDAT); _out.put(buffer, 0, ptr); _out.putInt(crc32(crcFrom, _out)); ptr = 0; len = buffer.length; } } if (row >= pic.getHeight()) break; rowData[0] = 0; // no filter for (int i = 1; i <= pic.getWidth() * 3; i += 3, bptr += 3) { rowData[i] = (byte) (pix[bptr] + 128); rowData[i + 1] = (byte) (pix[bptr + 1] + 128); rowData[i + 2] = (byte) (pix[bptr + 2] + 128); } deflater.setInput(rowData); if (row >= pic.getHeight() - 1) deflater.finish(); } if (ptr > 0) { _out.putInt(ptr); crcFrom = _out.duplicate(); _out.putInt(TAG_IDAT); _out.put(buffer, 0, ptr); _out.putInt(crc32(crcFrom, _out)); } _out.putInt(0); _out.putInt(TAG_IEND); _out.putInt(0xae426082); _out.flip(); return new EncodedFrame(_out, true); } @Override public ColorSpace[] getSupportedColorSpaces() { return new ColorSpace[] { ColorSpace.RGB }; } @Override public int estimateBufferSize(Picture8Bit frame) { return frame.getWidth() * frame.getHeight() * 4; } }