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

ru.r2cloud.ssdv.McuDecoder Maven / Gradle / Ivy

The newest version!
package ru.r2cloud.ssdv;

import java.util.Arrays;

class McuDecoder {

	private static final int MAX_SAMPLING = 2;
	static final int PIXELS_PER_MCU = DataUnitDecoder.PIXELS_PER_DU * MAX_SAMPLING;
	private static final int DEFAULT_SAMPLING = 1;
	private static final int[] DEFAULT_Y_DU = new int[DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU];
	private static final int[] DEFAULT_CBCR_DU = new int[DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU];
	// map 8x8 -> 16x16
	private static final int[] SRC_1X1;
	// map 8x8 -> 8x16
	private static final int[] SRC_2X1;
	// map 8x8 -> 16x8
	private static final int[] SRC_1X2;
	// map 8x8 -> 8x8
	private static final int[] SRC_2X2;

	// map 8x8 to 1 of 4 quadrants within 16x16
	private static final int[] DST_INDEX_MAPPING;

	private final ColorComponent yComponent = new ColorComponent();
	private final ColorComponent cbComponent = new ColorComponent();
	private final ColorComponent crComponent = new ColorComponent();

	private int[] rgb;
	private int mcuHeight;
	private int mcuWidth;

	static {
		// missing MCU = black RGB
		Arrays.fill(DEFAULT_CBCR_DU, 128);
		SRC_1X1 = setup1x1Table();
		SRC_1X2 = setup1x2Table();
		SRC_2X1 = setup2x1Table();
		SRC_2X2 = setup2x2Table();
		DST_INDEX_MAPPING = setupDstMappingTable();
	}

	public void reset(SsdvPacket firstPacket) {
		switch (firstPacket.getSubsamplingMode()) {
		case 0:
			yComponent.reset(0, MAX_SAMPLING, MAX_SAMPLING);
			break;
		case 1:
			yComponent.reset(1, 1, MAX_SAMPLING);
			break;
		case 2:
			yComponent.reset(2, MAX_SAMPLING, 1);
			break;
		case 3:
			yComponent.reset(3, 1, 1);
			break;
		default:
			throw new IllegalArgumentException("unsupported subsampling mode: " + firstPacket.getSubsamplingMode());
		}
		// Cb and Cr always have 1x1 in ssdv
		cbComponent.reset(3, DEFAULT_SAMPLING, DEFAULT_SAMPLING);
		crComponent.reset(3, DEFAULT_SAMPLING, DEFAULT_SAMPLING);

		if (rgb == null || rgb.length != yComponent.getBuffer().length) {
			rgb = new int[yComponent.getBuffer().length];
		}

		mcuHeight = yComponent.getMaxRows() * DataUnitDecoder.PIXELS_PER_DU;
		mcuWidth = yComponent.getMaxCols() * DataUnitDecoder.PIXELS_PER_DU;
	}

	// append until mcu full
	// return decoded rgb
	public int[] append(int[] du) {
		if (!yComponent.isFull()) {
			appendDu(du, yComponent);
			return null;
		}
		if (!cbComponent.isFull()) {
			appendDu(du, cbComponent);
			return null;
		}
		if (!crComponent.isFull()) {
			appendDu(du, crComponent);
			// last component needs one more du (unlikely)
			if (!crComponent.isFull()) {
				return null;
			}
		}

		int[] cbCrIndexMapping = getSrcIndexMapping(yComponent.getSubsamplingMode());
		for (int row = 0; row < yComponent.getMaxRows() * DataUnitDecoder.PIXELS_PER_DU; row++) {
			for (int col = 0; col < yComponent.getMaxCols() * DataUnitDecoder.PIXELS_PER_DU; col++) {
				int sourceIndex = row * yComponent.getMaxCols() * DataUnitDecoder.PIXELS_PER_DU + col;
				rgb[sourceIndex] = convertToRgb(yComponent.getBuffer()[sourceIndex], cbComponent.getBuffer()[cbCrIndexMapping[sourceIndex]], crComponent.getBuffer()[cbCrIndexMapping[sourceIndex]]);
			}
		}

		yComponent.reset();
		cbComponent.reset();
		crComponent.reset();

		return rgb;
	}

	private static int convertToRgb(int y, int cb, int cr) {
		double r = 1.402 * (cr - 128) + y;
		double g = -0.34414 * (cb - 128) - 0.71414 * (cr - 128) + y;
		double b = 1.772 * (cb - 128) + y;
		return (clip(r) << 16) | (clip(g) << 8) | clip(b);
	}

	private static int clip(double color) {
		if (color > 255) {
			return 255;
		} else if (color < 0) {
			return 0;
		} else {
			return (int) color;
		}
	}

	// can be called multiple times
	private static void appendDu(int[] du, ColorComponent component) {
		// shortcut for cbcr and 1x1 mapping
		if (du.length == component.getBuffer().length) {
			System.arraycopy(du, 0, component.getBuffer(), 0, du.length);
		} else {
			int offset = (component.getCurrentRow() * component.getMaxCols() + component.getCurrentCol()) * DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU;
			for (int i = 0; i < du.length; i++) {
				component.getBuffer()[DST_INDEX_MAPPING[offset + i]] = du[i];
			}
		}

		component.incrementCol();
		if (component.getCurrentCol() >= component.getMaxCols()) {
			component.incrementRow();
			if (component.getCurrentRow() < component.getMaxRows()) {
				component.setCurrentCol(0);
			}
		}
	}

	public int[] finishCurrentMcu() {
		int[] result = null;
		while (result == null && !Thread.currentThread().isInterrupted()) {
			if (isYComponent()) {
				result = append(DEFAULT_Y_DU);
			} else {
				result = append(DEFAULT_CBCR_DU);
			}
		}
		return result;
	}

	public int[] createEmptyMcu() {
		Arrays.fill(rgb, 0);
		return rgb;
	}

	public boolean isYComponent() {
		return !yComponent.isFull();
	}

	public boolean isCbComponent() {
		return yComponent.isFull() && !cbComponent.isFull();
	}

	public boolean isCrComponent() {
		return yComponent.isFull() && cbComponent.isFull() && !crComponent.isFull();
	}

	@Override
	public String toString() {
		return "McuDecoder [y=" + yComponent + ", cb=" + cbComponent + ", cr=" + crComponent + "]";
	}

	private static int[] setup1x1Table() {
		int[] table = new int[McuDecoder.PIXELS_PER_MCU * McuDecoder.PIXELS_PER_MCU];
		for (int i = 0, dsti = 0; i < DataUnitDecoder.PIXELS_PER_DU; i++) {
			for (int j = 0, dstj = 0; j < DataUnitDecoder.PIXELS_PER_DU; j++) {
				table[dsti * McuDecoder.PIXELS_PER_MCU + dstj] = i * DataUnitDecoder.PIXELS_PER_DU + j;
				table[dsti * McuDecoder.PIXELS_PER_MCU + dstj + 1] = i * DataUnitDecoder.PIXELS_PER_DU + j;
				table[(dsti + 1) * McuDecoder.PIXELS_PER_MCU + dstj] = i * DataUnitDecoder.PIXELS_PER_DU + j;
				table[(dsti + 1) * McuDecoder.PIXELS_PER_MCU + dstj + 1] = i * DataUnitDecoder.PIXELS_PER_DU + j;
				dstj += MAX_SAMPLING;
			}
			dsti += MAX_SAMPLING;
		}
		return table;
	}

	private static int[] setup2x1Table() {
		int[] table = new int[MAX_SAMPLING * DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU];
		for (int row = 0, dstRow = 0; row < DataUnitDecoder.PIXELS_PER_DU; row++) {
			for (int col = 0, dstCol = 0; col < DataUnitDecoder.PIXELS_PER_DU; col++) {
				table[dstRow * McuDecoder.PIXELS_PER_MCU + dstCol] = row * DataUnitDecoder.PIXELS_PER_DU + col;
				table[dstRow * McuDecoder.PIXELS_PER_MCU + dstCol + 1] = row * DataUnitDecoder.PIXELS_PER_DU + col;
				dstCol += MAX_SAMPLING;
			}
			dstRow += 1;
		}
		return table;
	}

	private static int[] setup1x2Table() {
		int[] table = new int[MAX_SAMPLING * DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU];
		for (int row = 0, dstRow = 0; row < DataUnitDecoder.PIXELS_PER_DU; row++) {
			for (int col = 0, dstCol = 0; col < DataUnitDecoder.PIXELS_PER_DU; col++) {
				table[dstRow * DataUnitDecoder.PIXELS_PER_DU + dstCol] = row * DataUnitDecoder.PIXELS_PER_DU + col;
				table[(dstRow + 1) * DataUnitDecoder.PIXELS_PER_DU + dstCol] = row * DataUnitDecoder.PIXELS_PER_DU + col;
				dstCol += 1;
			}
			dstRow += MAX_SAMPLING;
		}
		return table;
	}

	// not an actual mapping,
	// but useful for getSrcIndexMapping()
	private static int[] setup2x2Table() {
		int[] result = new int[DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU];
		for (int i = 0; i < result.length; i++) {
			result[i] = i;
		}
		return result;
	}

	private static int[] getSrcIndexMapping(int subsamplingMode) {
		switch (subsamplingMode) {
		case 0:
			return SRC_1X1;
		case 1:
			return SRC_1X2;
		case 2:
			return SRC_2X1;
		case 3:
			return SRC_2X2;
		default:
			throw new IllegalArgumentException("unsupported subsampling mode: " + subsamplingMode);
		}
	}

	private static int[] setupDstMappingTable() {
		int[] table = new int[McuDecoder.PIXELS_PER_MCU * McuDecoder.PIXELS_PER_MCU];
		int cur = 0;
		for (int yRow = 0; yRow < MAX_SAMPLING; yRow++) {
			for (int yCol = 0; yCol < MAX_SAMPLING; yCol++) {
				for (int duRow = 0; duRow < DataUnitDecoder.PIXELS_PER_DU; duRow++) {
					for (int duCol = 0; duCol < DataUnitDecoder.PIXELS_PER_DU; duCol++) {
						int colIndex = yCol * DataUnitDecoder.PIXELS_PER_DU + duCol;
						int rowIndex = yRow * MAX_SAMPLING * DataUnitDecoder.PIXELS_PER_DU * DataUnitDecoder.PIXELS_PER_DU + duRow * MAX_SAMPLING * DataUnitDecoder.PIXELS_PER_DU;
						table[cur] = rowIndex + colIndex;
						cur++;
					}
				}
			}
		}
		return table;
	}

	public int getMcuHeight() {
		return mcuHeight;
	}

	public int getMcuWidth() {
		return mcuWidth;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy