package org.jcodec.common.model;
import static java.lang.System.arraycopy;
import static org.jcodec.common.model.ColorSpace.MAX_PLANES;
import java.lang.IllegalArgumentException;
import java.util.Arrays;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* The data is -128 shifted, so 0 is represented by -128 and 255 is represented
* by +127
*
* @author The JCodec project
*
*/
public class Picture8Bit {
private ColorSpace color;
private int width;
private int height;
private byte[][] data;
private Rect crop;
public static Picture8Bit createPicture8Bit(int width, int height, byte[][] data, ColorSpace color) {
return new Picture8Bit(width, height, data, color, new Rect(0, 0, width, height));
}
public Picture8Bit(int width, int height, byte[][] data, ColorSpace color, Rect crop) {
this.width = width;
this.height = height;
this.data = data;
this.color = color;
this.crop = crop;
if (color != null) {
for (int i = 0; i < color.nComp; i++) {
int mask = 0xff >> (8 - color.compWidth[i]);
if ((width & mask) != 0)
throw new IllegalArgumentException("Component " + i + " width should be a multiple of "
+ (1 << color.compWidth[i]) + " for colorspace: " + color);
if (crop != null && (crop.getWidth() & mask) != 0)
throw new IllegalArgumentException("Component " + i + " cropped width should be a multiple of "
+ (1 << color.compWidth[i]) + " for colorspace: " + color);
mask = 0xff >> (8 - color.compHeight[i]);
if ((height & mask) != 0)
throw new IllegalArgumentException("Component " + i + " height should be a multiple of "
+ (1 << color.compHeight[i]) + " for colorspace: " + color);
if (crop != null && (crop.getHeight() & mask) != 0)
throw new IllegalArgumentException("Component " + i + " cropped height should be a multiple of "
+ (1 << color.compHeight[i]) + " for colorspace: " + color);
}
}
}
public static Picture8Bit copyPicture8Bit(Picture8Bit other) {
return new Picture8Bit(other.width, other.height, other.data, other.color, other.crop);
}
public static Picture8Bit create(int width, int height, ColorSpace colorSpace) {
return createCropped(width, height, colorSpace, null);
}
public static Picture8Bit createCropped(int width, int height, ColorSpace colorSpace, Rect crop) {
int[] planeSizes = new int[MAX_PLANES];
for (int i = 0; i < colorSpace.nComp; i++) {
planeSizes[colorSpace.compPlane[i]] += (width >> colorSpace.compWidth[i])
* (height >> colorSpace.compHeight[i]);
}
int nPlanes = 0;
for (int i = 0; i < MAX_PLANES; i++)
nPlanes += planeSizes[i] != 0 ? 1 : 0;
byte[][] data = new byte[nPlanes][];
for (int i = 0, plane = 0; i < MAX_PLANES; i++) {
if (planeSizes[i] != 0) {
data[plane++] = new byte[planeSizes[i]];
}
}
return new Picture8Bit(width, height, data, colorSpace, crop);
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public byte[] getPlaneData(int plane) {
return data[plane];
}
public ColorSpace getColor() {
return color;
}
public byte[][] getData() {
return data;
}
public Rect getCrop() {
return crop;
}
public int getPlaneWidth(int plane) {
return width >> color.compWidth[plane];
}
public int getPlaneHeight(int plane) {
return height >> color.compHeight[plane];
}
public boolean compatible(Picture8Bit src) {
return src.color == color && src.width == width && src.height == height;
}
public Picture8Bit createCompatible() {
return Picture8Bit.create(width, height, color);
}
public void copyFrom(Picture8Bit src) {
if (!compatible(src))
throw new IllegalArgumentException("Can not copy to incompatible picture");
for (int plane = 0; plane < color.nComp; plane++) {
if (data[plane] == null)
continue;
arraycopy(src.data[plane], 0, data[plane], 0, (width >> color.compWidth[plane])
* (height >> color.compHeight[plane]));
}
}
/**
* Creates a cropped clone of this picture.
*
* @return
*/
public Picture8Bit cloneCropped() {
if (cropNeeded()) {
return cropped();
} else {
Picture8Bit clone = createCompatible();
clone.copyFrom(this);
return clone;
}
}
public Picture8Bit cropped() {
if (!cropNeeded())
return this;
Picture8Bit result = Picture8Bit.create(crop.getWidth(), crop.getHeight(), color);
if(color.planar) {
for (int plane = 0; plane < data.length; plane++) {
if (data[plane] == null)
continue;
cropSub(data[plane], crop.getX() >> color.compWidth[plane], crop.getY() >> color.compHeight[plane],
crop.getWidth() >> color.compWidth[plane], crop.getHeight() >> color.compHeight[plane],
width >> color.compWidth[plane], crop.getWidth() >> color.compWidth[plane], result.data[plane]);
}
} else {
cropSub(data[0], crop.getX(), crop.getY(), crop.getWidth(),
crop.getHeight(), width * color.nComp, crop.getWidth() * color.nComp, result.data[0]);
}
return result;
}
protected boolean cropNeeded() {
return crop != null
&& (crop.getX() != 0 || crop.getY() != 0 || crop.getWidth() != width || crop.getHeight() != height);
}
private void cropSub(byte[] src, int x, int y, int w, int h, int srcStride, int dstStride, byte[] tgt) {
int srcOff = y * srcStride + x, dstOff = 0;
for (int i = 0; i < h; i++) {
for (int j = 0; j < dstStride; j++)
tgt[dstOff + j] = src[srcOff + j];
srcOff += srcStride;
dstOff += dstStride;
}
}
public void setCrop(Rect crop) {
this.crop = crop;
}
public int getCroppedWidth() {
return crop == null ? width : crop.getWidth();
}
public int getCroppedHeight() {
return crop == null ? height : crop.getHeight();
}
public static Picture8Bit fromPicture(Picture pic) {
Picture8Bit create = Picture8Bit.createCropped(pic.getWidth(), pic.getHeight(), pic.getColor(), pic.getCrop());
for (int i = 0; i < Math.min(pic.getData().length, create.getData().length); i++) {
for (int j = 0; j < Math.min(pic.getData()[i].length, create.getData()[i].length); j++) {
create.getData()[i][j] = (byte) (((pic.getData()[i][j] << 8) >> pic.getBitDepth()) - 128);
}
}
return create;
}
public Picture toPicture(int bitDepth) {
Picture create = Picture.doCreate(width, height, color, bitDepth, crop);
return toPictureInternal(bitDepth, create);
}
public Picture toPictureWithBuffer(int bitDepth, int[][] buffer) {
Picture create = new Picture(width, height, buffer, color, bitDepth, crop);
return toPictureInternal(bitDepth, create);
}
private Picture toPictureInternal(int bitDepth, Picture create) {
for (int i = 0; i < data.length; i++) {
int planeSize = getPlaneWidth(i) * getPlaneHeight(i);
for (int j = 0; j < planeSize; j++) {
create.getData()[i][j] = ((data[i][j] + 128) << bitDepth) >> 8;
}
}
return create;
}
public void fill(int val) {
for (int i = 0; i < data.length; i++) {
Arrays.fill(data[i], (byte) val);
}
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Picture8Bit))
return false;
Picture8Bit other = (Picture8Bit) obj;
if (other.getCroppedWidth() != getCroppedWidth() || other.getCroppedHeight() != getCroppedHeight()
|| other.getColor() != color)
return false;
for (int i = 0; i < getData().length; i++)
if (!planeEquals(other, i))
return false;
return true;
}
private boolean planeEquals(Picture8Bit other, int plane) {
int cw = color.compWidth[plane];
int ch = color.compHeight[plane];
int offA = other.getCrop() == null ? 0 : ((other.getCrop().getX() >> cw) + (other.getCrop().getY() >> ch)
* (other.getWidth() >> cw));
int offB = crop == null ? 0 : ((crop.getX() >> cw) + (crop.getY() >> ch) * (width >> cw));
byte[] planeData = other.getPlaneData(plane);
for (int i = 0; i < getCroppedHeight() >> ch; i++, offA += (other.getWidth() >> cw), offB += (width >> cw)) {
for (int j = 0; j < getCroppedWidth() >> cw; j++) {
if (planeData[offA + j] != data[plane][offB + j])
return false;
}
}
return true;
}
}