All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.sbtqa.monte.media.avi.DIBCodec Maven / Gradle / Ivy

There is a newer version: 1.1.0-JAVA7
Show newest version
/* @(#)DIBCodec.java
 * Copyright © 2011-2012 Werner Randelshofer, Switzerland.
 * You may only use this software in accordance with the license terms.
 */
package ru.sbtqa.monte.media.avi;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.OutputStream;
import ru.sbtqa.monte.media.AbstractVideoCodec;
import ru.sbtqa.monte.media.Buffer;
import static ru.sbtqa.monte.media.BufferFlag.*;
import ru.sbtqa.monte.media.Format;
import ru.sbtqa.monte.media.FormatKeys.MediaType;
import static ru.sbtqa.monte.media.VideoFormatKeys.*;
import ru.sbtqa.monte.media.io.SeekableByteArrayOutputStream;

/**
 * {@code DIBCodec} encodes a BufferedImage as a Microsoft Device Independent
 * Bitmap (DIB) into a byte array.
 * 
 * The DIB codec only works with the AVI file format. Other file formats, such
 * as QuickTime, use a different encoding for uncompressed video.
 * 
 * This codec currently only supports encoding from a {@code BufferedImage} into
 * the file format. Decoding support may be added in the future.
 * 
 * This codec does not encode the color palette of an image. This must be done
 * separately.
 * 
 * The pixels of a frame are written row by row from bottom to top and from the
 * left to the right. 24-bit pixels are encoded as BGR.
 * 
 * Supported input formats:
 *  {@code Format} with {@code BufferedImage.class}, any width, any height,
 * depth=4.
 * Supported output formats:
 *  {@code Format} with {@code byte[].class}, same width and height as input
 * format, depth=4.
 *
 * @author Werner Randelshofer
 * @version $Id: DIBCodec.java 363 2016-11-09 19:32:30Z werner $
 */
public class DIBCodec extends AbstractVideoCodec {

    public DIBCodec() {
        super(new Format[]{
            new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
            EncodingKey, ENCODING_BUFFERED_IMAGE, FixedFrameRateKey, true), //
            new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
            EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
            FixedFrameRateKey, true, DepthKey, 4), //
            new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
            EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
            FixedFrameRateKey, true, DepthKey, 8), //
            new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
            EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
            FixedFrameRateKey, true, DepthKey, 24), //
        },
              new Format[]{
                  new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_JAVA,
                        EncodingKey, ENCODING_BUFFERED_IMAGE, FixedFrameRateKey, true), //
                  new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
                        EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
                        FixedFrameRateKey, true, DepthKey, 4), //
                  new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
                        EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
                        FixedFrameRateKey, true, DepthKey, 8), //
                  new Format(MediaTypeKey, MediaType.VIDEO, MimeTypeKey, MIME_AVI,
                        EncodingKey, ENCODING_AVI_DIB, DataClassKey, byte[].class,
                        FixedFrameRateKey, true, DepthKey, 24), //
              });
    }

    @Override
    public Format setOutputFormat(Format f) {
        super.setOutputFormat(f);

        // This codec can not scale an image.
        // Enforce these properties
        if (outputFormat != null) {
            if (inputFormat != null) {
                outputFormat = outputFormat.prepend(inputFormat.intersectKeys(WidthKey, HeightKey, DepthKey));
            }
        }
        return this.outputFormat;
    }

    @Override
    public int process(Buffer in, Buffer out) {
        if (outputFormat.get(EncodingKey) == ENCODING_BUFFERED_IMAGE) {
            return decode(in, out);
        } else {
            return encode(in, out);
        }
    }

    public int decode(Buffer in, Buffer out) {
        out.setMetaTo(in);
        out.format = outputFormat;
        if (in.isFlag(DISCARD)) {
            return CODEC_OK;
        }

        out.sampleCount = 1;
        BufferedImage img = null;

        int imgType;
        switch (outputFormat.get(DepthKey)) {
            case 4:
                imgType = BufferedImage.TYPE_BYTE_INDEXED;
                break;
            case 8:
                imgType = BufferedImage.TYPE_BYTE_INDEXED;
                break;
            case 24:
                imgType = BufferedImage.TYPE_INT_RGB;
                break;
            default:
                imgType = BufferedImage.TYPE_INT_RGB;
                break;
        }

        if (out.data instanceof BufferedImage) {
            img = (BufferedImage) out.data;
            // Fixme: Handle sub-image
            if (img.getWidth() != outputFormat.get(WidthKey)
                  || img.getHeight() != outputFormat.get(HeightKey)
                  || img.getType() != imgType) {
                img = null;
            }
        }
        if (img == null) {
            img = new BufferedImage(outputFormat.get(WidthKey), outputFormat.get(HeightKey), imgType);
        }
        out.data = img;

        switch (outputFormat.get(DepthKey)) {
            case 4:
                readKey4((byte[]) in.data, in.offset, in.length, img);
                break;
            case 8:
                readKey8((byte[]) in.data, in.offset, in.length, img);
                break;
            case 24:
            default:
                readKey24((byte[]) in.data, in.offset, in.length, img);
                break;
        }

        return CODEC_OK;
    }

    public int encode(Buffer in, Buffer out) {
        out.setMetaTo(in);
        out.format = outputFormat;
        if (in.isFlag(DISCARD)) {
            return CODEC_OK;
        }

        SeekableByteArrayOutputStream tmp;
        if (out.data instanceof byte[]) {
            tmp = new SeekableByteArrayOutputStream((byte[]) out.data);
        } else {
            tmp = new SeekableByteArrayOutputStream();
        }

        // Handle sub-image
        // FIXME - Scanline stride must be a multiple of four.
        Rectangle r;
        int scanlineStride;
        if (in.data instanceof BufferedImage) {
            BufferedImage image = (BufferedImage) in.data;
            WritableRaster raster = image.getRaster();
            scanlineStride = raster.getSampleModel().getWidth();
            r = raster.getBounds();
            r.x -= raster.getSampleModelTranslateX();
            r.y -= raster.getSampleModelTranslateY();
            out.header = image.getColorModel();
        } else {
            r = new Rectangle(0, 0, outputFormat.get(WidthKey), outputFormat.get(HeightKey));
            scanlineStride = outputFormat.get(WidthKey);
            out.header = null;
        }

        try {
            switch (outputFormat.get(DepthKey)) {
                case 4: {
                    byte[] pixels = getIndexed8(in);
                    if (pixels == null) {
                        out.setFlag(DISCARD);
                        return CODEC_OK;
                    }
                    writeKey4(tmp, pixels, r.width, r.height, r.x + r.y * scanlineStride, scanlineStride);
                    break;
                }
                case 8: {
                    byte[] pixels = getIndexed8(in);
                    if (pixels == null) {
                        out.setFlag(DISCARD);
                        return CODEC_OK;
                    }
                    writeKey8(tmp, pixels, r.width, r.height, r.x + r.y * scanlineStride, scanlineStride);
                    break;
                }
                case 24: {
                    int[] pixels = getRGB24(in);
                    if (pixels == null) {
                        out.setFlag(DISCARD);
                        return CODEC_OK;
                    }
                    writeKey24(tmp, pixels, r.width, r.height, r.x + r.y * scanlineStride, scanlineStride);
                    break;
                }
                default:
                    out.setFlag(DISCARD);
                    return CODEC_OK;
            }

            out.setFlag(KEYFRAME);
            out.data = tmp.getBuffer();
            out.sampleCount = 1;
            out.offset = 0;
            out.length = (int) tmp.getStreamPosition();
            return CODEC_OK;
        } catch (IOException ex) {
            ex.printStackTrace();
            out.setFlag(DISCARD);
            return CODEC_FAILED;
        }
    }

    public void readKey4(byte[] in, int offset, int length, BufferedImage img) {
        DataBufferByte buf = (DataBufferByte) img.getRaster().getDataBuffer();
        WritableRaster raster = img.getRaster();
        int scanlineStride = raster.getSampleModel().getWidth();
        Rectangle r = raster.getBounds();
        r.x -= raster.getSampleModelTranslateX();
        r.y -= raster.getSampleModelTranslateY();

        throw new UnsupportedOperationException("readKey4 not yet implemented");
    }

    public void readKey8(byte[] in, int offset, int length, BufferedImage img) {
        DataBufferByte buf = (DataBufferByte) img.getRaster().getDataBuffer();
        WritableRaster raster = img.getRaster();
        int scanlineStride = raster.getSampleModel().getWidth();
        Rectangle r = raster.getBounds();
        r.x -= raster.getSampleModelTranslateX();
        r.y -= raster.getSampleModelTranslateY();

        int h = img.getHeight();
        int w = img.getWidth();
        int i = offset;
        int j = r.x + r.y * scanlineStride + (h - 1) * scanlineStride;
        byte[] out = buf.getData();
        for (int y = 0; y < h; y++) {
            System.arraycopy(in, i, out, j, w);
            i += w;
            j -= scanlineStride;
        }
    }

    public void readKey24(byte[] in, int offset, int length, BufferedImage img) {
        DataBufferInt buf = (DataBufferInt) img.getRaster().getDataBuffer();
        WritableRaster raster = img.getRaster();
        int scanlineStride = raster.getSampleModel().getWidth();
        Rectangle r = raster.getBounds();
        r.x -= raster.getSampleModelTranslateX();
        r.y -= raster.getSampleModelTranslateY();

        int h = img.getHeight();
        int w = img.getWidth();
        int i = offset;
        int j = r.x + r.y * scanlineStride + (h - 1) * scanlineStride;
        int[] out = buf.getData();
        for (int y = 0; y < h; y++) {
//            System.arraycopy(in,i,out,j,w);
            for (int k = 0; k < w; k++) {
                out[j + k] = 0xff000000//alpha
                      | ((in[i + k * 3 + 1] & 0xff) << 0)//blue
                      | ((in[i + k * 3 + 2] & 0xff) << 8)//green
                      | ((in[i + k + 3] & 0xff) << 16);//red
            }
            i += w;
            j -= scanlineStride;
        }
    }

    /**
     * Encodes a 4-bit key frame.
     *
     * @param out The output stream.
     * @param pixels The image data.
     * @param offset The offset to the first pixel in the data array.
     * @param height TODO
     * @param width The width of the image in data elements.
     * @param scanlineStride The number to append to offset to get to the next
     * scanline.
     * @throws java.io.IOException TODO
     */
    public void writeKey4(OutputStream out, byte[] pixels, int width, int height, int offset, int scanlineStride)
          throws IOException {

        byte[] bytes = new byte[width];
        for (int y = (height - 1) * scanlineStride; y >= 0; y -= scanlineStride) { // Upside down
            for (int x = offset, xx = 0, n = offset + width; x < n; x += 2, ++xx) {
                bytes[xx] = (byte) (((pixels[y + x] & 0xf) << 4) | (pixels[y + x + 1] & 0xf));
            }
            out.write(bytes);
        }

    }

    /**
     * Encodes an 8-bit key frame.
     *
     * @param out The output stream.
     * @param pixels The image data.
     * @param offset The offset to the first pixel in the data array.
     * @param height TODO
     * @param width The width of the image in data elements.
     * @param scanlineStride The number to append to offset to get to the next
     * scanline.
     * @throws java.io.IOException TODO
     */
    public void writeKey8(OutputStream out, byte[] pixels, int width, int height, int offset, int scanlineStride)
          throws IOException {

        for (int y = (height - 1) * scanlineStride; y >= 0; y -= scanlineStride) { // Upside down
            out.write(pixels, y + offset, width);
        }
    }

    /**
     * Encodes a 24-bit key frame.
     *
     * @param out The output stream.
     * @param pixels The image data.
     * @param offset The offset to the first pixel in the data array.
     * @param height TODO
     * @param width The width of the image in data elements.
     * @param scanlineStride The number to append to offset to get to the next
     * scanline.
     * @throws java.io.IOException TODO
     */
    public void writeKey24(OutputStream out, int[] pixels, int width, int height, int offset, int scanlineStride)
          throws IOException {
        int w3 = width * 3;
        byte[] bytes = new byte[w3]; // holds a scanline of raw image data with 3 channels of 8 bit data
        for (int xy = (height - 1) * scanlineStride + offset; xy >= offset; xy -= scanlineStride) { // Upside down
            for (int x = 0, xp = 0; x < w3; x += 3, ++xp) {
                int p = pixels[xy + xp];
                bytes[x] = (byte) (p); // Blue
                bytes[x + 1] = (byte) (p >> 8); // Green
                bytes[x + 2] = (byte) (p >> 16); // Red
            }
            out.write(bytes);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy