This commit is contained in:
2026-07-26 22:35:06 +08:00
commit c92cf914fc
58 changed files with 5954 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/graalvm-jdk-24+36.1">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="java.base/jdk.internal.foreign=ALL-UNNAMED"/>
<attribute name="add-opens" value="java.base/jdk.internal.foreign=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/KNEOptimize.jar"/>
<classpathentry kind="lib" path="lib/aircompressor-2.0.3.jar" sourcepath="lib/aircompressor-2.0.3-sources.jar"/>
<classpathentry kind="lib" path="lib/aircompressor-2.0.3-javadoc.jar"/>
<classpathentry kind="lib" path="lib/aircompressor-2.0.3-sources.jar"/>
<classpathentry kind="lib" path="lib/aircompressor-2.0.3-tests.jar" sourcepath="lib/aircompressor-2.0.3-test-sources.jar"/>
<classpathentry kind="lib" path="lib/aircompressor-2.0.3-test-sources.jar"/>
<classpathentry kind="lib" path="lib/JavaOpenCL64.jar"/>
<classpathentry kind="lib" path="lib/jcodec-0.2.5.jar" sourcepath="lib/jcodec-0.2.5-sources.jar"/>
<classpathentry kind="lib" path="lib/jcodec-0.2.5-javadoc.jar"/>
<classpathentry kind="lib" path="lib/jcodec-0.2.5-sources.jar"/>
<classpathentry kind="lib" path="lib/jcodec-javase-0.2.5.jar"/>
<classpathentry kind="lib" path="lib/jcodec-javase-0.2.5-javadoc.jar"/>
<classpathentry kind="lib" path="lib/jcodec-javase-0.2.5-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+5
View File
@@ -0,0 +1,5 @@
/4K-Test-SDR/
/input2/
/input3/
/PngSuite-2017jul19.zip
/input/
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>KVFCodec</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
+14
View File
@@ -0,0 +1,14 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=23
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=23
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=23
+1
View File
@@ -0,0 +1 @@
/org/
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+177
View File
@@ -0,0 +1,177 @@
package org.kne.codec.kif;
import org.kne.membandboost.MembandBoost;
/**
* 位平面编码工具
* 将字节数组按位平面重排:先存所有最高位,再存次高位,...,最后存最低位
* 例如:输入 [0x01, 0x02, 0x03]
* 二进制: 00000001, 00000010, 00000011
* 位平面拆分: bit7: 000, bit6: 000, ..., bit1: 011, bit0: 101
* 输出: [00000000, 00000000, ..., 00000011, 00000101]
*/
public class BitPlane {
/**
* 位平面编码(矩阵转置风格)
*
* 核心逻辑:
* 对于输出数组的每个字节,依次从输入数组的8个位平面各取1个bit
* 即:输出字节的 bit i = 输入数组第 i 个位平面的当前位
*
* 等价于:把 8×len 的 bit 矩阵转置为 len×8 的 bit 矩阵
*/
public static byte[] encodePacked(byte[] input) {
int len = input.length;
byte[] output = MembandBoost.allocateUninitializedByteArray(len);
// 输出数组的每个字节,由8个位平面的各1个bit组成
for (int outIdx = 0; outIdx < len; outIdx++) {
int outByte = 0;
// 从8个位平面各取1个bit,组装成一个字节
for (int plane = 0; plane < 8; plane++) {
// 当前位平面:bitShift
// 从输入数组的 inputPos 位置取 bit
int bitpos=(outIdx<<3)+plane;
int inputPos = bitpos%len;
int bitShift = bitpos/len;
int bit = (input[inputPos] >> (7-bitShift)) & 1;
outByte |= (bit << plane); // 放到输出字节的第 plane 位
}
output[outIdx] = (byte) outByte;
}
return output;
}
/**
* 位平面编码(极速版)
* 按位平面批量处理,利用位平面内部的连续性
*/
/* public static byte[] encodePacked(byte[] input) {
int len = input.length;
byte[] output = new byte[len];
for (int plane = 0; plane < 8; plane++) {
int bit = 7 - plane;
int planeStart = plane * len;
int planeByteStart = planeStart >> 3;
int planeBitOffset = planeStart & 7;
// 处理当前位平面
// 收集位平面数据到一个 int(最多 32 位),批量写入
for (int i = 0; i < len; i++) {
int inputBit = (input[i] >> bit) & 1;
if (inputBit == 1) {
int bytePos = planeByteStart + ((i + planeBitOffset) >> 3);
int bitPos = 7 - ((i + planeBitOffset) & 7);
output[bytePos] |= (1 << bitPos);
}
}
}
return output;
}*/
/**
* 位平面解码(矩阵转置逆操作)
*
* 核心逻辑:
* 对于输出数组的每个字节,从输入数组的8个字节中各取1个bit
* 即:输出字节的 bit i = 输入数组第 i 个字节的第 plane 位
*/
public static byte[] decodePacked(byte[] input) {
byte[] output = MembandBoost.allocateUninitializedByteArray(input.length);
// 输出数组的每个字节,由8个输入字节的对应bit组成
for (int outIdx = 0; outIdx < output.length; outIdx++) {
int outByte = 0;
for (int plane = 0; plane < 8; plane++) {
// 从输入数组的当前字节取第 bitShift 位
int bitpos2=plane*input.length+outIdx;
int inputpos=bitpos2>>3;
int inputshift=bitpos2&0b111;
int bit = (input[inputpos] >> inputshift) & 1;
outByte |= (bit << (7-plane));
}
output[outIdx] = (byte) outByte;
}
return output;
}
/**
* 位平面解码(极速版)
*/
/*public static byte[] decodePacked(byte[] input) {
byte[] output = new byte[length];
for (int plane = 0; plane < 8; plane++) {
int bit = 7 - plane;
int planeStart = plane * len;
int planeByteStart = planeStart >> 3;
int planeBitOffset = planeStart & 7;
for (int i = 0; i < len; i++) {
int bytePos = planeByteStart + ((i + planeBitOffset) >> 3);
int bitPos = 7 - ((i + planeBitOffset) & 7);
int inputBit = (input[bytePos] >> bitPos) & 1;
if (inputBit == 1) {
output[i] |= (1 << bit);
}
}
}
return output;
}*/
public static void main(String[] args) {
// 测试数据:0-255 的字节
byte[] testData = new byte[16];
for (int i = 0; i < testData.length; i++) {
testData[i] = (byte) i;
}
// 打包版编码
byte[] encoded = BitPlane.encodePacked(testData);
System.out.println("原始长度: " + testData.length);
System.out.println("编码后长度: " + encoded.length);
// 解码
byte[] decoded = BitPlane.decodePacked(encoded);
// 查看位平面结构
System.out.println("\n前 16 个字节的测试数据:");
for (int i = 0; i < 16; i++) {
System.out.printf("%02X ", testData[i]);
}
System.out.println("\n前 16 个字节的位平面编码:");
for (int i = 0; i < 16; i++) {
System.out.printf("%02X ", encoded[i]);
}
System.out.println("\n前 16 个字节的位平面解码:");
for (int i = 0; i < 16; i++) {
System.out.printf("%02X ", decoded[i]);
}
// 验证
boolean ok = true;
for (int i = 0; i < testData.length; i++) {
if (testData[i] != decoded[i]) {
ok = false;
System.out.println("❌ 第 " + i + " 个字节不一致");
break;
}
}
System.out.println("无损还原: " + (ok ? "" : ""));
}
}
+50
View File
@@ -0,0 +1,50 @@
package org.kne.codec.kif;
import java.util.ArrayList;
import java.util.List;
/**
* 分块坐标信息(仅包含位置和尺寸,不包含实际像素数据)
*/
public class BlockCoord {
private final int posX;
private final int posY;
private final int width;
private final int height;
public BlockCoord(int posX, int posY, int width, int height) {
this.posX = posX;
this.posY = posY;
this.width = width;
this.height = height;
}
public int getPosX() { return posX; }
public int getPosY() { return posY; }
public int getWidth() { return width; }
public int getHeight() { return height; }
@Override
public String toString() {
return String.format("BlockCoord(%d,%d,%dx%d)", posX, posY, width, height);
}
// ==================== 分块坐标计算 ====================
public static List<BlockCoord> calculateBlockCoords(int width, int height, int blockWidth, int blockHeight) {
int blocksX = (width + blockWidth - 1) / blockWidth;
int blocksY = (height + blockHeight - 1) / blockHeight;
List<BlockCoord> coords = new ArrayList<>(blocksX * blocksY);
for (int by = 0; by < blocksY; by++) {
for (int bx = 0; bx < blocksX; bx++) {
int posX = bx * blockWidth;
int posY = by * blockHeight;
int bw = Math.min(blockWidth, width - posX);
int bh = Math.min(blockHeight, height - posY);
coords.add(new BlockCoord(posX, posY, bw, bh));
}
}
return coords;
}
}
@@ -0,0 +1,70 @@
package org.kne.codec.kif;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import org.kne.membandboost.MembandBoost;
public class BufferedImageMembandBoost {
private static final int[] MASK=new int[]{0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};// R, G, B, A 掩码
/**
* 创建一个不归零的 TYPE_INT_ARGB BufferedImage
* 使用 MembandBoost 分配未初始化的像素数组
*/
public static BufferedImage createUninitializedBufferedImage(int width, int height) {
int size = width * height;
// 1. 创建未初始化的 DataBuffer(不归零!)
DataBufferInt db = new DataBufferInt(
MembandBoost.allocateUninitializedIntArray(size),
size
);
// 2. 创建 ARGB Raster4 波段,对应 TYPE_INT_ARGB
WritableRaster raster = Raster.createPackedRaster(
db,
width, height,
width,
MASK,
null
);
// 3. 创建匹配的 ARGB ColorModel
ColorModel colorModel = ColorModel.getRGBdefault();
// 4. 创建 BufferedImage
return new BufferedImage(
colorModel,
raster,
false, // isRasterPremultiplied
null
);
}
/**
* 从已有的 int[] 像素数据创建 BufferedImage(不拷贝)
*/
public static BufferedImage createFromPixels(int[] pixels, int width, int height) {
int size = width * height;
if (pixels.length < size) {
throw new IllegalArgumentException("pixels 数组长度不足");
}
DataBufferInt db = new DataBufferInt(pixels, size);
WritableRaster raster = Raster.createPackedRaster(
db,
width, height,
width,
MASK,
null
);
ColorModel colorModel = ColorModel.getRGBdefault();
return new BufferedImage(colorModel, raster, false, null);
}
}
@@ -0,0 +1,199 @@
package org.kne.codec.kif;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.processing.SupportedSourceVersion;
/**
* 字节值频率统计与映射表生成工具
* 用于调色板模式中的字节级概率空间映射
*/
public class ByteFrequencyMapper {
/**
* 统计字节数组中每个值(0~255)出现的次数
*
* @param data 输入的字节数组
* @return 长度为 256 的 int 数组,index 表示字节值,value 表示出现次数
*/
public static int[] buildFrequency(byte[] data) {
int[] freq = new int[256];
for (byte b : data) {
freq[b & 0xFF]++;
}
return freq;
}
/**
* 根据频率数组生成映射表(按频率降序排列)
* 映射表:index -> byte value
* 即:映射表[0] = 出现次数最多的字节值
*
* @param freq 长度为 256 的频率数组
* @return 长度为 256 的 byte 数组,按频率降序排列
*/
public static byte[] buildInverseMap(int[] freq) {
// 创建索引数组 0~255
Integer[] indices = new Integer[256];
for (int i = 0; i < 256; i++) {
indices[i] = i;
}
// 按频率降序排序
Arrays.sort(indices, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
// 频率高的排前面
int cmp = Integer.compare(freq[b], freq[a]);
if (cmp != 0) return cmp;
// 频率相同,按值升序
return Integer.compare(a, b);
}
});
// 生成映射表
byte[] forwardMap = new byte[256];
for (int i = 0; i < 256; i++) {
forwardMap[i] = (byte) indices[i].intValue();
}
return forwardMap;
}
/**
* 生成逆映射表
* 逆映射表:byte value -> index
* 即:inverseMap[原始字节值] = 在映射表中的位置
*
* @param map 正向映射表(index -> byte value
* @return 长度为 256 的 byte 数组,逆映射表
*/
public static byte[] inverse(byte[] map) {
byte[] inverseMap = new byte[256];
for (int i = 0; i < map.length; i++) {
int value = map[i] & 0xFF;
inverseMap[value] = (byte) i;
}
return inverseMap;
}
/**
* 使用映射表对字节数组进行映射(编码)
*
* @param data 原始字节数组
* @param forwardMap 映射表
* @return 映射后的字节数组
*/
public static byte[] applyMap(byte[] data, byte[] forwardMap) {
byte[] result = new byte[data.length];
for (int i = 0; i < data.length; i++) {
int value = data[i] & 0xFF;
// 查找该值在映射表中的位置
// 注意:如果 forwardMap 是压缩的(只包含部分值),需要处理未映射的情况
// 这里假设 forwardMap 是全映射
result[i] = forwardMap[value];
}
return result;
}
/**
* 截断映射表:只保留前 K 个高频值,其余映射到最近的高频值
*
* @param freq 频率数组
* @param k 保留的高频值数量(1~256)
* @return 截断后的映射表(长度仍然为 256,但只包含 K 种唯一值)
*/
public static byte[] buildTruncatedForwardMap(int[] freq, int k) {
if (k <= 0 || k > 256) {
throw new IllegalArgumentException("k 必须在 1~256 之间");
}
// 获取完整映射表
byte[] fullMap = buildInverseMap(freq);
// 取前 k 个高频值
byte[] truncated = new byte[256];
// 初始化为 0(默认映射到第一个高频值)
Arrays.fill(truncated, fullMap[0]);
// 将前 k 个高频值放入映射表
for (int i = 0; i < k; i++) {
truncated[i] = fullMap[i];
}
// 对于低频值,需要建立逆映射:原始字节值 -> 最近的高频值索引
// 这里简化为:低频值全部映射到索引 0(最高频值)
// 更精细的做法可以是:找到最近的映射表值
return truncated;
}
/**
* 获取映射表的统计信息
*/
public static String getMapStats(byte[] forwardMap, int[] freq) {
int total = 0;
for (int f : freq) {
total += f;
}
StringBuilder sb = new StringBuilder();
sb.append("映射表统计:\n");
sb.append(" 唯一值数: ").append(getUniqueCount(forwardMap)).append("\n");
long covered = 0;
int threshold = 99;
for (int i = 0; i < forwardMap.length; i++) {
int value = forwardMap[i] & 0xFF;
covered += freq[value];
double pct = covered * 100.0 / total;
if (pct >= threshold && i > 0) {
sb.append(String.format(" 前 %d 个值覆盖 %.2f%% 的像素\n", i + 1, pct));
break;
}
}
double pct = covered * 100.0 / total;
sb.append(String.format(" 全部值覆盖 %.2f%% 的像素\n", pct));
return sb.toString();
}
private static int getUniqueCount(byte[] map) {
boolean[] seen = new boolean[256];
int count = 0;
for (byte b : map) {
int value = b & 0xFF;
if (!seen[value]) {
seen[value] = true;
count++;
}
}
return count;
}
public static void main(String[] args) {
byte[] serialData = new byte[] {1,1,1,1,1,0,0,0,2,3,3,3,3,3,3,3,4};
int[]freq=ByteFrequencyMapper.buildFrequency(serialData);
byte[]inverse=ByteFrequencyMapper.buildInverseMap(freq);
byte[]foward=ByteFrequencyMapper.inverse(inverse);
byte[] serialDatafor= ByteFrequencyMapper.applyMap(serialData, foward);
System.out.println("输入:");
ByteStatisticsCollector bsc=new ByteStatisticsCollector();
bsc.analyze(serialData);
System.out.println(bsc);
System.out.println("编码:");
ByteStatisticsCollector bsc2=new ByteStatisticsCollector();
bsc2.analyze(serialDatafor);
System.out.println(bsc2);
byte[] serialDatainv= ByteFrequencyMapper.applyMap(serialDatafor, inverse);
System.out.println("解码:");
ByteStatisticsCollector bsc32=new ByteStatisticsCollector();
bsc32.analyze(serialDatainv);
System.out.println(bsc32);
}
}
@@ -0,0 +1,98 @@
package org.kne.codec.kif;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
* 字节统计收集器
* 统计 byte[] 中不同字节值的出现次数与概率(0~255)
*
* 用于分析像素数据、残差数据、位平面数据的分布特性
*/
public class ByteStatisticsCollector extends StatisticsCollector<Byte> {
private static final ByteFormatter FORMATTER = new ByteFormatter();
/**
* 统计整个字节数组
*/
public void analyze(byte[] data) {
for (byte b : data) {
addRecord(b);
}
}
/**
* 统计字节数组的指定范围
*/
public void analyze(byte[] data, int offset, int length) {
int end = Math.min(offset + length, data.length);
for (int i = offset; i < end; i++) {
addRecord(data[i]);
}
}
/**
* 统计后返回最常见的字节值
*/
public byte getMostCommonByte() {
Map<Byte, AtomicLong> sorted = getSorted();
if (sorted.isEmpty()) {
return 0;
}
return sorted.entrySet().iterator().next().getKey();
}
/**
* 统计后返回最常见字节值的出现次数
*/
public long getMostCommonCount() {
Map<Byte, AtomicLong> sorted = getSorted();
if (sorted.isEmpty()) {
return 0;
}
return sorted.entrySet().iterator().next().getValue().get();
}
/**
* 统计后返回最常见字节值的占比(百分比)
*/
public double getMostCommonProbability() {
byte mostCommon = getMostCommonByte();
return getProbability(mostCommon);
}
/**
* 获取字节值对应的可读字符串
*/
public String byteToString(byte b) {
int v = b & 0xFF;
return String.format("0x%02X (%d)", v, v);
}
/**
* 生成字节统计的表格报告
*/
@Override
public String toString() {
return formatTable(FORMATTER);
}
/**
* 生成 CSV 报告
*/
public String toCSV() {
return super.toCSV(FORMATTER);
}
/**
* Byte 格式化器
*/
private static class ByteFormatter implements ItemFormatter<Byte> {
@Override
public String format(Byte item) {
int v = item & 0xFF;
return String.format("0x%02X", v);
}
}
}
@@ -0,0 +1,201 @@
package org.kne.codec.kif;
import java.awt.Color;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
* 颜色统计收集器
* 用于统计图像中各种颜色出现的频率
* 继承自 StatisticsCollector<Color>
*/
public class ColorStatisticsCollector extends StatisticsCollector<Color> {
public void analyze(int[] pixels) {
for (int i : pixels) {
addRecord(i);
}
}
/**
* 记录一个 RGB 整数值(0x00RRGGBB)的颜色
*/
public void addRecord(int rgb) {
Color color = new Color(rgb);
addRecord(color);
}
/**
* 记录一个 RGB 整数值(0x00RRGGBB)的颜色,多次
*/
public void addRecord(int rgb, long count) {
Color color = new Color(rgb);
addRecord(color, count);
}
/**
* 记录一个由 R、G、B 分量组成的颜色
*/
public void addRecord(int r, int g, int b) {
Color color = new Color(r & 0xFF, g & 0xFF, b & 0xFF);
addRecord(color);
}
/**
* 获取某个 RGB 颜色的出现次数
*/
public long getCount(int rgb) {
return getCount(new Color(rgb));
}
/**
* 获取某个 RGB 颜色的出现概率(百分比)
*/
public double getProbability(int rgb) {
return getProbability(new Color(rgb));
}
/**
* 获取最常见的颜色(出现次数最多的颜色)
*/
public Color getMostFrequentColor() {
Map<Color, AtomicLong> sorted = getSorted();
if (sorted.isEmpty()) {
return null;
}
return sorted.entrySet().iterator().next().getKey();
}
/**
* 获取最罕见的颜色(出现次数最少的颜色)
*/
public Color getLeastFrequentColor() {
Map<Color, AtomicLong> sorted = getSortedAscending();
if (sorted.isEmpty()) {
return null;
}
return sorted.entrySet().iterator().next().getKey();
}
/**
* 获取前 N 种最常见的颜色
*/
public Map<Color, AtomicLong> getTopColors(int n) {
Map<Color, AtomicLong> sorted = getSorted();
return sorted.entrySet().stream()
.limit(n)
.collect(java.util.stream.Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(old, neu) -> old,
java.util.LinkedHashMap::new
));
}
/**
* 获取唯一颜色数量
*/
public int getColorCount() {
return getUniqueCount();
}
/**
* 生成颜色统计报告
*/
public String formatColorTable() {
return formatTable(new ItemFormatter<Color>() {
@Override
public String format(Color color) {
return String.format("RGB(%d,%d,%d)",
color.getRed(),
color.getGreen(),
color.getBlue()
);
}
});
}
/**
* 生成颜色统计报告(带颜色名称/简写)
*/
public String formatColorTable(boolean showName) {
if (!showName) {
return formatColorTable();
}
long t = total.get();
if (t == 0) {
return "(无统计数据)";
}
StringBuilder sb = new StringBuilder();
sb.append("╔═══════════════════════════════════════════════════════════════════════════════════╗\n");
sb.append("║ 颜色统计报告 ║\n");
sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
sb.append("║ 序号 │ 颜色 │ 出现次数 │ 占比 │ 累积占比 ║\n");
sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
Map<Color, AtomicLong> sorted = getSorted();
long cumulative = 0;
int index = 0;
for (Map.Entry<Color, AtomicLong> entry : sorted.entrySet()) {
Color color = entry.getKey();
long count = entry.getValue().get();
double pct = count * 100.0 / t;
cumulative += count;
double cumPct = cumulative * 100.0 / t;
index++;
String colorStr = String.format("RGB(%d,%d,%d)",
color.getRed(),
color.getGreen(),
color.getBlue()
);
if (colorStr.length() > 27) {
colorStr = colorStr.substring(0, 24) + "...";
}
sb.append(String.format("║ %4d │ %-27s │ %8d │ %6.2f%% │ %6.2f%% ║\n",
index,
colorStr,
count,
pct,
cumPct
));
}
sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
sb.append(String.format("║ 总像素数: %d │ 唯一颜色数: %d │ 覆盖率: %6.2f%% ║\n",
t,
getUniqueCount(),
100.0
));
sb.append("╚═══════════════════════════════════════════════════════════════════════════════════╝");
return sb.toString();
}
/**
* 生成简洁的颜色统计摘要
*/
public String getSummary() {
long t = total.get();
if (t == 0) {
return "无数据";
}
Color mostFrequent = getMostFrequentColor();
Color leastFrequent = getLeastFrequentColor();
return String.format(
"总像素数: %d, 唯一颜色数: %d, 最频繁颜色: RGB(%d,%d,%d) (%.2f%%), 最罕见颜色: RGB(%d,%d,%d) (%.2f%%)",
t,
getUniqueCount(),
mostFrequent.getRed(), mostFrequent.getGreen(), mostFrequent.getBlue(),
getProbability(mostFrequent),
leastFrequent.getRed(), leastFrequent.getGreen(), leastFrequent.getBlue(),
getProbability(leastFrequent)
);
}
}
+125
View File
@@ -0,0 +1,125 @@
package org.kne.codec.kif;
import org.kne.membandboost.MembandBoost;
public class ColorTransform {
// ==================== RGB 版本(3通道) ====================
/**
* R-G 和 B-G 跨通道颜色变换(RGB)
* 编码:R' = R - G, G' = G, B' = B - G
*/
public static int[] colorTransform(int[] input, int w, int h) {
int[] output = MembandBoost.allocateUninitializedIntArray(input.length);
for (int i = 0; i < input.length; i++) {
int rgb = input[i];
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
byte rg = (byte) (r - g);
byte bg = (byte) (b - g);
output[i] = ((rg & 0xFF) << 16) | ((g & 0xFF) << 8) | (bg & 0xFF);
}
return output;
}
public static int[] colorInverse(int[] input, int w, int h) {
int[] output = MembandBoost.allocateUninitializedIntArray(input.length);
for (int i = 0; i < input.length; i++) {
int packed = input[i];
int rg = (packed >> 16) & 0xFF;
int g = (packed >> 8) & 0xFF;
int bg = packed & 0xFF;
int r = g + rg;
int b = g + bg;
output[i] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
return output;
}
public static void colorTransformInPlace(int[] input, int w, int h) {
for (int i = 0; i < input.length; i++) {
int rgb = input[i];
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
byte rg = (byte) (r - g);
byte bg = (byte) (b - g);
input[i] = ((rg & 0xFF) << 16) | ((g & 0xFF) << 8) | (bg & 0xFF);
}
}
public static void colorInverseInPlace(int[] input, int w, int h) {
for (int i = 0; i < input.length; i++) {
int packed = input[i];
int rg = (packed >> 16) & 0xFF;
int g = (packed >> 8) & 0xFF;
int bg = packed & 0xFF;
int r = g + rg;
int b = g + bg;
input[i] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
}
// ==================== RGBA 版本(4通道,Alpha 原封不动) ====================
/**
* RGBA 颜色变换:R' = R - G, G' = G, B' = B - G, A' = A(原封不动)
*/
public static int[] colorTransformRGBA(int[] input, int w, int h) {
int[] output = MembandBoost.allocateUninitializedIntArray(input.length);
for (int i = 0; i < input.length; i++) {
int pixel = input[i];
int r = (pixel >> 16) & 0xFF;
int g = (pixel >> 8) & 0xFF;
int b = pixel & 0xFF;
int a = (pixel >> 24) & 0xFF; // Alpha 原封不动
byte rg = (byte) (r - g);
byte bg = (byte) (b - g);
output[i] = ((a & 0xFF) << 24) | ((rg & 0xFF) << 16) | ((g & 0xFF) << 8) | (bg & 0xFF);
}
return output;
}
public static int[] colorInverseRGBA(int[] input, int w, int h) {
int[] output = MembandBoost.allocateUninitializedIntArray(input.length);
for (int i = 0; i < input.length; i++) {
int packed = input[i];
int a = (packed >> 24) & 0xFF;
int rg = (packed >> 16) & 0xFF;
int g = (packed >> 8) & 0xFF;
int bg = packed & 0xFF;
int r = g + rg;
int b = g + bg;
output[i] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
return output;
}
public static void colorTransformRGBAInPlace(int[] input, int w, int h) {
for (int i = 0; i < input.length; i++) {
int pixel = input[i];
int r = (pixel >> 16) & 0xFF;
int g = (pixel >> 8) & 0xFF;
int b = pixel & 0xFF;
int a = (pixel >> 24) & 0xFF;
byte rg = (byte) (r - g);
byte bg = (byte) (b - g);
input[i] = ((a & 0xFF) << 24) | ((rg & 0xFF) << 16) | ((g & 0xFF) << 8) | (bg & 0xFF);
}
}
public static void colorInverseRGBAInPlace(int[] input, int w, int h) {
for (int i = 0; i < input.length; i++) {
int packed = input[i];
int a = (packed >> 24) & 0xFF;
int rg = (packed >> 16) & 0xFF;
int g = (packed >> 8) & 0xFF;
int bg = packed & 0xFF;
int r = g + rg;
int b = g + bg;
input[i] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
}
}
@@ -0,0 +1,455 @@
package org.kne.codec.kif;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* 图像压缩格式对比实验
* 遍历 input 目录下的所有图片,用 ImageIO 注册的所有格式进行压缩,
* 输出压缩后大小、压缩率,并验证无损/有损。
* 分别记录编码时间和解码时间
*/
public class CompressionExperiment {
// 支持的输入格式(ImageIO 能读取的)
private static final Set<String> INPUT_FORMATS = new HashSet<>(Arrays.asList(
"png", "bmp", "kif"
));
// 要测试的输出格式(从 ImageIO 注册的 Writer 中获取)
private static Set<String> outputFormats=new HashSet<>(Arrays.asList(
"png", "kif"
));
public static void main(String[] args) throws Exception {
// 🔥 强制覆盖标准输出流为 UTF-8
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
// 1. 注册 KIF 插件
org.kne.codec.kif.KIFImageReaderSpi.register();
org.kne.codec.kif.KIFImageWriterSpi.register();
// 2. 获取所有可用的输出格式
System.out.println("=== 可用的输出格式 ===");
outputFormats.forEach(f -> System.out.println(" " + f));
System.out.println();
// 3. 准备目录
Path inputDir = Paths.get("input2");
Path outputDir = Paths.get("output");
if (!Files.exists(inputDir)) {
System.err.println("错误: input 目录不存在!");
return;
}
if (!Files.exists(outputDir)) {
Files.createDirectories(outputDir);
}
// 4. 获取所有测试图片
List<Path> imageFiles = getTestImages(inputDir);
if (imageFiles.isEmpty()) {
System.err.println("错误: input 目录中没有找到图片!");
return;
}
System.out.println("找到 " + imageFiles.size() + " 张测试图片\n");
// 5.预热
warmup(outputDir, imageFiles,30000000000L);
// 6. 执行实验
List<ExperimentResult> results = runExperiment(outputDir, imageFiles);
// 7. 输出汇总报告
printSummary(results);
System.out.println(KIFImageWriter.getModeCollector());
}
private static void warmup(Path outputDir, List<Path> imageFiles, long warmupTimeNs) {
long startTime = System.nanoTime();
long elapsed = 0;
int iteration = 0;
System.out.println("========================================");
System.out.println("🔥 预热开始 (目标: " + (warmupTimeNs / 1_000_000) + "ms)");
System.out.println("========================================");
loop:while(true) {
for (Path imageFile : imageFiles) {
if(elapsed >= warmupTimeNs) {
break loop;
}
long iterStart = System.nanoTime();
try {
BufferedImage original = ImageIO.read(imageFile.toFile());
if (original == null) {
continue;
}
// 对所有格式进行预热压缩和解压(丢弃结果)
for (String format : outputFormats) {
try {
// 执行完整的编码+解码流程,但不保存结果
Path tempFile = outputDir.resolve("warmup_" + iteration + "_" +
imageFile.getFileName().toString().replaceAll("\\.[^.]*$", "") +
"." + format.toLowerCase());
// 编码
boolean written = ImageIO.write(original, format, tempFile.toFile());
if (!written) {
continue;
}
// 解码(验证无损性)
BufferedImage decoded = ImageIO.read(tempFile.toFile());
if (decoded != null) {
// 简单比较尺寸,不逐像素比较(节省时间)
if (decoded.getWidth() == original.getWidth() &&
decoded.getHeight() == original.getHeight()) {
// 预热成功
}
}
// 立即删除临时文件
Files.deleteIfExists(tempFile);
} catch (Exception e) {
// 预热时忽略错误
}
}
} catch (Exception e) {
// 忽略预热错误
}
iteration++;
long iterEnd = System.nanoTime();
elapsed = iterEnd - startTime;
}
}
System.out.println("========================================");
System.out.printf("🔥 预热完成: %d 张图片, 耗时 %dms\n",
iteration, elapsed / 1_000_000L);
System.out.println("========================================");
// 清理临时文件
try {
Files.list(outputDir)
.filter(p -> p.getFileName().toString().startsWith("warmup_"))
.forEach(p -> {
try { Files.deleteIfExists(p); } catch (Exception e) {}
});
} catch (Exception e) {
// 忽略清理错误
}
}
private static List<ExperimentResult> runExperiment(Path outputDir, List<Path> imageFiles) {
List<ExperimentResult> results = new ArrayList<>();
for (Path imageFile : imageFiles) {
try {
// 读取原图
long readStart = System.nanoTime();
BufferedImage original = ImageIO.read(imageFile.toFile());
long readEnd = System.nanoTime();
long readTimeMs = (readEnd - readStart) / 1_000_000;
if (original == null) {
System.err.println(" ⚠️ 无法读取:"+imageFile.getFileName()+",跳过\n");
continue;
}
System.out.println("========================================");
System.out.println("测试图片: " + imageFile.getFileName()+" ("+original.getWidth()+"x"+original.getHeight()+")");
System.out.println("========================================");
long originalSize = Files.size(imageFile);
System.out.printf(" 原始大小: %,d bytes (%.2f KB) | 读取耗时: %dms\n",
originalSize, originalSize / 1024.0, readTimeMs);
// 对每种输出格式进行测试
for (String format : outputFormats) {
try {
ExperimentResult result = testFormat(original, imageFile, format, outputDir);
results.add(result);
System.out.println(result);
} catch (Exception e) {
System.err.println("" + format + " 测试失败: " );
e.printStackTrace();
}
}
System.out.println();
} catch (Exception e) {
System.err.println(" ❌ 处理 " + imageFile.getFileName() + " 时出错: " );
e.printStackTrace();
}
}
return results;
}
/**
* 获取所有可用的图像写入格式
*/
private static Set<String> getAvailableWriterFormats() {
Set<String> formats = new HashSet<>();
IIORegistry registry = IIORegistry.getDefaultInstance();
Iterator<ImageWriterSpi> iter = registry.getServiceProviders(ImageWriterSpi.class, true);
while (iter.hasNext()) {
ImageWriterSpi spi = iter.next();
String[] names = spi.getFormatNames();
if (names != null) {
for (String name : names) {
formats.add(name.toLowerCase());
}
}
}
return formats;
}
/**
* 获取 input 目录下的所有图片文件
*/
private static List<Path> getTestImages(Path inputDir) throws IOException {
List<Path> images = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(inputDir)) {
for (Path entry : stream) {
if (Files.isRegularFile(entry)) {
String fileName = entry.getFileName().toString().toLowerCase();
for (String fmt : INPUT_FORMATS) {
if (fileName.endsWith("." + fmt)) {
images.add(entry);
break;
}
}
}
}
}
images.sort(Comparator.comparing(p -> p.getFileName().toString()));
return images;
}
/**
* 测试一种格式的压缩效果(纯内存测试,排除硬盘 I/O)
* 分别记录编码时间和解码时间
*/
private static ExperimentResult testFormat(BufferedImage original, Path sourceFile,
String format, Path outputDir) throws Exception {
// 生成输出文件名(仅用于显示和保存验证)
String baseName = sourceFile.getFileName().toString();
int dotIdx = baseName.lastIndexOf('.');
String nameWithoutExt = (dotIdx > 0) ? baseName.substring(0, dotIdx) : baseName;
String outputFileName = nameWithoutExt + "." + format.toLowerCase();
Path outputFile = outputDir.resolve(outputFileName);
// ===== 编码时间(纯内存) =====
long encodeStart = System.nanoTime();
// 使用 ByteArrayOutputStream 替代 FileOutputStream
int estimatedSize = (int) (original.getWidth()*original.getHeight()*4 * 1.5);
ByteArrayOutputStream baos = new ByteArrayOutputStream(estimatedSize);
boolean written = ImageIO.write(original, format, baos);
long encodeEnd = System.nanoTime();
long encodeTimeMs = (encodeEnd - encodeStart) / 1_000_000;
if (!written) {
throw new IOException("ImageIO.write() 返回 false,可能不支持此格式");
}
// 压缩后大小(从内存中获取)
byte[] encodedData = baos.toByteArray();
long compressedSize = encodedData.length;
long originalSize = Files.size(sourceFile);
double compressionRatio = (double) compressedSize / originalSize * 100;
// ===== 解码时间(纯内存) =====
long decodeStart = System.nanoTime();
// 使用 ByteArrayInputStream 替代 FileInputStream
ByteArrayInputStream bais = new ByteArrayInputStream(encodedData);
BufferedImage decoded = ImageIO.read(bais);
long decodeEnd = System.nanoTime();
long decodeTimeMs = (decodeEnd - decodeStart) / 1_000_000;
// 验证无损性
boolean lossless = compareImages(original, decoded);
// ===== 可选:将结果保存到磁盘(用于调试/验证) =====
// 如果需要实际保存文件以便后续查看,取消注释下面的代码
// try (FileOutputStream fos = new FileOutputStream(outputFile.toFile())) {
// fos.write(encodedData);
// }
return new ExperimentResult(
sourceFile.getFileName().toString(),
format,
originalSize,
compressedSize,
compressionRatio,
encodeTimeMs,
decodeTimeMs,
lossless
);
}
/**
* 逐像素比较两张图片是否完全相同
*/
private static boolean compareImages(BufferedImage img1, BufferedImage img2) {
if (img1 == null || img2 == null) return false;
if (img1.getWidth() != img2.getWidth()) return false;
if (img1.getHeight() != img2.getHeight()) return false;
int width = img1.getWidth();
int height = img1.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (img1.getRGB(x, y) != img2.getRGB(x, y)) {
return false;
}
}
}
return true;
}
/**
* 打印汇总报告
*/
private static void printSummary(List<ExperimentResult> results) {
System.out.println("\n\n");
System.out.println("╔═══════════════════════════════════════════════════════════════════════════════════════════════════╗");
System.out.println("║ 汇总报告 ║");
System.out.println("╚═══════════════════════════════════════════════════════════════════════════════════════════════════╝");
// 按格式分组统计
Map<String, List<ExperimentResult>> grouped = results.stream()
.collect(Collectors.groupingBy(r -> r.format));
// 表头 - 压缩率统计
System.out.println("\n┌──────────┬────────────┬──────────────┬─────────────┬────────────┬──────────┐");
System.out.println("│ 格式 │ 测试数量 │ 平均压缩率 │ 最大压缩率 │ 最小压缩率 │ 无损率 │");
System.out.println("├──────────┼────────────┼──────────────┼─────────────┼────────────┼──────────┤");
for (Map.Entry<String, List<ExperimentResult>> entry : grouped.entrySet()) {
String format = entry.getKey();
List<ExperimentResult> list = entry.getValue();
double avgRatio = list.stream().mapToDouble(r -> r.compressionRatio).average().orElse(0);
double maxRatio = list.stream().mapToDouble(r -> r.compressionRatio).max().orElse(0);
double minRatio = list.stream().mapToDouble(r -> r.compressionRatio).min().orElse(0);
long losslessCount = list.stream().filter(r -> r.lossless).count();
double losslessRate = (double) losslessCount / list.size() * 100;
System.out.printf("│ %-8s│ %10d│ %12.2f%%│ %11.2f%%│ %10.2f%%│ %8.1f%%│\n",
format, list.size(), avgRatio, maxRatio, minRatio, losslessRate);
}
System.out.println("└──────────┴────────────┴──────────────┴─────────────┴────────────┴──────────┘");
// 表头 - 时间统计
System.out.println("\n┌──────────┬────────────┬──────────────┬──────────────┬──────────────────┐");
System.out.println("│ 格式 │ 测试数量 │ 平均编码时间 │ 平均解码时间 │ 编码/解码比 │");
System.out.println("├──────────┼────────────┼──────────────┼──────────────┼──────────────────┤");
for (Map.Entry<String, List<ExperimentResult>> entry : grouped.entrySet()) {
String format = entry.getKey();
List<ExperimentResult> list = entry.getValue();
double avgEncode = list.stream().mapToLong(r -> r.encodeTimeMs).average().orElse(0);
double avgDecode = list.stream().mapToLong(r -> r.decodeTimeMs).average().orElse(0);
double ratio = avgDecode > 0 ? avgEncode / avgDecode : 0;
System.out.printf("│ %-8s│ %10d│ %12.1fms│ %12.1fms│ %8.2fx │\n",
format, list.size(), avgEncode, avgDecode, ratio);
}
System.out.println("└──────────┴────────────┴──────────────┴──────────────┴──────────────────┘");
// KIF 专项统计
List<ExperimentResult> kifResults = results.stream()
.filter(r -> "kif".equalsIgnoreCase(r.format))
.collect(Collectors.toList());
if (!kifResults.isEmpty()) {
System.out.println("\n📊 KIF 格式详细统计:");
System.out.println(" 测试图片数: " + kifResults.size());
double avgKifRatio = kifResults.stream().mapToDouble(r -> r.compressionRatio).average().orElse(0);
System.out.printf(" 平均压缩率: %.2f%% (相对于原始文件)\n", avgKifRatio);
long losslessKif = kifResults.stream().filter(r -> r.lossless).count();
System.out.println(" 无损图片数: " + losslessKif + "/" + kifResults.size());
double avgKifEncode = kifResults.stream().mapToLong(r -> r.encodeTimeMs).average().orElse(0);
double avgKifDecode = kifResults.stream().mapToLong(r -> r.decodeTimeMs).average().orElse(0);
System.out.printf(" 平均编码时间: %.1f ms\n", avgKifEncode);
System.out.printf(" 平均解码时间: %.1f ms\n", avgKifDecode);
// 与 PNG 对比
List<ExperimentResult> pngResults = results.stream()
.filter(r -> "png".equalsIgnoreCase(r.format))
.collect(Collectors.toList());
if (!pngResults.isEmpty() && kifResults.size() == pngResults.size()) {
double pngAvg = pngResults.stream().mapToDouble(r -> r.compressionRatio).average().orElse(0);
System.out.printf(" PNG 平均压缩率: %.2f%%\n", pngAvg);
System.out.printf(" KIF vs PNG: %.2f%% (KIF 比 PNG %s %.2f%%)\n",
avgKifRatio,
avgKifRatio < pngAvg ? "" : "",
Math.abs(avgKifRatio - pngAvg));
double pngAvgEncode = pngResults.stream().mapToLong(r -> r.encodeTimeMs).average().orElse(0);
double pngAvgDecode = pngResults.stream().mapToLong(r -> r.decodeTimeMs).average().orElse(0);
System.out.printf(" PNG 平均编码时间: %.1f ms\n", pngAvgEncode);
System.out.printf(" PNG 平均解码时间: %.1f ms\n", pngAvgDecode);
System.out.printf(" KIF 编码是 PNG 的 %.2f 倍\n", avgKifEncode / pngAvgEncode);
System.out.printf(" KIF 解码是 PNG 的 %.2f 倍\n", avgKifDecode / pngAvgDecode);
}
}
}
/**
* 实验结果记录类
*/
static class ExperimentResult {
String sourceFileName;
String format;
long originalSize;
long compressedSize;
double compressionRatio;
long encodeTimeMs;
long decodeTimeMs;
boolean lossless;
ExperimentResult(String sourceFileName, String format, long originalSize,
long compressedSize, double compressionRatio,
long encodeTimeMs, long decodeTimeMs, boolean lossless) {
this.sourceFileName = sourceFileName;
this.format = format;
this.originalSize = originalSize;
this.compressedSize = compressedSize;
this.compressionRatio = compressionRatio;
this.encodeTimeMs = encodeTimeMs;
this.decodeTimeMs = decodeTimeMs;
this.lossless = lossless;
}
@Override
public String toString() {
String status = lossless ? "✅ 无损" : "⚠️ 有损";
return String.format(" %-6s | %,8d bytes (%.1f KB) | 压缩率: %5.1f%% | 编码: %4dms | 解码: %4dms | %s",
format, compressedSize, compressedSize / 1024.0,
compressionRatio, encodeTimeMs, decodeTimeMs, status);
}
}
}
+94
View File
@@ -0,0 +1,94 @@
package org.kne.codec.kif;
import io.airlift.compress.Compressor;
import io.airlift.compress.Decompressor;
import io.airlift.compress.thirdparty.JdkDeflateCompressor;
import io.airlift.compress.thirdparty.JdkInflateDecompressor;
import io.airlift.compress.thirdparty.ZstdJniCompressor;
import io.airlift.compress.zstd.ZstdCompressor;
import io.airlift.compress.zstd.ZstdDecompressor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.kne.membandboost.MembandBoost;
/**
* 压缩/解压缩工具
* 支持 DeflateZLIB)和 Zstandard 两种算法
*/
public class Compressors {
// ==================== Zstandard (Zstd) ====================
private static final ThreadLocal<ZstdCompressor> ZSTD_COMPRESSOR =
ThreadLocal.withInitial(ZstdCompressor::new);
private static final ThreadLocal<ZstdDecompressor> ZSTD_DECOMPRESSOR =
ThreadLocal.withInitial(ZstdDecompressor::new);
// ==================== Deflate (ZLIB) ====================
private static final ThreadLocal<JdkDeflateCompressor> DEFLATE_COMPRESSOR =
ThreadLocal.withInitial(JdkDeflateCompressor::new);
private static final ThreadLocal<JdkInflateDecompressor> DEFLATE_DECOMPRESSOR =
ThreadLocal.withInitial(JdkInflateDecompressor::new);
/**
* 压缩
*/
public static byte[] compress(Compressor comp,byte[] input) {
int maxLen = comp.maxCompressedLength(input.length);
byte[] compressed = MembandBoost.allocateUninitializedByteArray(maxLen);
int compressedSize = comp.compress(
input, 0, input.length,
compressed, 0,
maxLen
);
//System.out.println("in:"+input.length+" out:"+compressedSize);
byte[] copy = MembandBoost.allocateUninitializedByteArray(compressedSize);
System.arraycopy(compressed, 0, copy, 0,
Math.min(compressed.length, compressedSize));
return copy;
}
/**
* 解压(已知原始长度)
*/
public static byte[] decompress(Decompressor decomp,byte[] input, int expectedLength) throws IOException {
try {
byte[] output = MembandBoost.allocateUninitializedByteArray(expectedLength);
int decompressedSize = decomp.decompress(
input, 0, input.length,
output, 0, expectedLength
);
if (decompressedSize != expectedLength) {
throw new IOException("解压长度不匹配: 期望 " + expectedLength + ", 实际 " + decompressedSize);
}
return output;
}catch(Exception e) {
e.printStackTrace();
System.out.println("in:"+input.length+" expected:"+expectedLength);
throw e;
}
}
public static byte[] compressZstd(byte[] input) {
return compress(ZSTD_COMPRESSOR.get(), input);
}
public static byte[] decompressZstd(byte[] input, int expectedLength) throws IOException {
return decompress(ZSTD_DECOMPRESSOR.get(), input, expectedLength);
}
public static byte[] compressDeflate(byte[] input) {
return compress(DEFLATE_COMPRESSOR.get(), input);
}
public static byte[] decompressDeflate(byte[] input, int expectedLength) throws IOException {
return decompress(DEFLATE_DECOMPRESSOR.get(), input, expectedLength);
}
}
+149
View File
@@ -0,0 +1,149 @@
package org.kne.codec.kif;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
/**
* 调试工具:将字节数组 dump 到磁盘文件
* 用于保存算法中间状态,方便用十六进制编辑器分析
*/
public class DebugDump {
// 调试开关:设为 false 可全局禁用 dump
private static boolean ENABLED = true;
// dump 文件根目录
private static final String DUMP_DIR = "debug";
/**
* 将字节数组 dump 到文件
* 文件名:debug/[uuid].dat
*
* @param data 要保存的字节数组
* @return 保存的文件路径,如果禁用则返回 null
*/
public static String dump(byte[] data) {
return dump(data, null);
}
/**
* 将字节数组 dump 到文件,附带说明标签
* 文件名:debug/[uuid]_[tag].dat
*
* @param data 要保存的字节数组
* @param tag 标签(如 "raw", "predicted", "compressed"),可为 null
* @return 保存的文件路径,如果禁用则返回 null
*/
public static String dump(byte[] data, String tag) {
if (!ENABLED) {
return null;
}
try {
// 确保目录存在
File dir = new File(DUMP_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
// 生成文件名
String uuid = UUID.randomUUID().toString().substring(0, 8);
String fileName = tag != null && !tag.isEmpty()
? uuid + "_" + tag + ".dat"
: uuid + ".dat";
File file = new File(dir, fileName);
// 写入数据
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(data);
}
// 同时输出一个 .info 文件,记录元数据
String infoFile = fileName.replace(".dat", ".info");
String infoContent = String.format(
"Dump Info:\n" +
" UUID: %s\n" +
" Tag: %s\n" +
" Size: %,d bytes (%.2f KB)\n" +
" Time: %s\n",
uuid,
tag != null ? tag : "(none)",
data.length,
data.length / 1024.0,
java.time.LocalDateTime.now()
);
Files.write(Paths.get(dir.getPath(), infoFile), infoContent.getBytes());
System.out.println("🔍 Dump: " + file.getAbsolutePath() + " (" + data.length + " bytes)");
return file.getAbsolutePath();
} catch (IOException e) {
System.err.println("❌ Dump 失败: " + e.getMessage());
return null;
}
}
/**
* 将 int 数组转为 byte 数组后 dump(小端序)
*/
public static String dumpIntArray(int[] data, String tag) {
byte[] bytes = new byte[data.length * 4];
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.order(ByteOrder.LITTLE_ENDIAN);
for (int v : data) {
bb.putInt(v);
}
return dump(bytes, tag);
}
/**
* 将 int 数组转为 byte 数组后 dump(大端序)
*/
public static String dumpIntArrayBE(int[] data, String tag) {
byte[] bytes = new byte[data.length * 4];
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.order(ByteOrder.BIG_ENDIAN);
for (int v : data) {
bb.putInt(v);
}
return dump(bytes, tag);
}
/**
* 将 short 数组转为 byte 数组后 dump(小端序)
*/
public static String dumpShortArray(short[] data, String tag) {
byte[] bytes = new byte[data.length * 2];
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.order(ByteOrder.LITTLE_ENDIAN);
for (short v : data) {
bb.putShort(v);
}
return dump(bytes, tag);
}
/**
* 启用/禁用 dump
*/
public static void setEnabled(boolean enabled) {
ENABLED = enabled;
}
/**
* 清空 debug 目录
*/
public static void clean() throws IOException {
File dir = new File(DUMP_DIR);
if (dir.exists()) {
for (File f : dir.listFiles()) {
f.delete();
}
}
}
}
+30
View File
@@ -0,0 +1,30 @@
package org.kne.codec.kif;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
public class ExceptionTool {
public static void throwIOException(Throwable t) throws IOException {
if (t == null) {
return;
}
// 递归展开 ExecutionException
while (t instanceof ExecutionException && t.getCause() != null) {
t = t.getCause();
}
if (t instanceof IOException) {
throw (IOException) t;
}
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof Error) {
throw (Error) t;
}
throw new IOException(t);
}
}
+50
View File
@@ -0,0 +1,50 @@
package org.kne.codec.kif;
public class ImageCopy {
/**
* 图像块拷贝:将源图像中的一个矩形区域拷贝到目标图像的指定位置
*
* @param src 源像素数组 (ARGB/RGB)
* @param srcw 源图像宽度
* @param srch 源图像高度
* @param dst 目标像素数组 (ARGB/RGB)
* @param dstw 目标图像宽度
* @param dsth 目标图像高度
* @param srcposx 源区域左上角 x 坐标
* @param srcposy 源区域左上角 y 坐标
* @param dstposx 目标区域左上角 x 坐标
* @param dstposy 目标区域左上角 y 坐标
* @param copyw 拷贝宽度
* @param copyh 拷贝高度
* @throws IllegalArgumentException 如果参数越界或无效
*/
public static void imageCopy(int[] src, int srcw, int srch,
int[] dst, int dstw, int dsth,
int srcposx, int srcposy,
int dstposx, int dstposy,
int copyw, int copyh) {
// 参数校验
if (src == null || dst == null) {
throw new IllegalArgumentException("源或目标数组不能为空");
}
if (srcposx < 0 || srcposy < 0 || dstposx < 0 || dstposy < 0) {
throw new IllegalArgumentException("起始坐标不能为负数");
}
if (copyw <= 0 || copyh <= 0) {
throw new IllegalArgumentException("拷贝宽度和高度必须大于0");
}
if (srcposx + copyw > srcw || srcposy + copyh > srch) {
throw new IllegalArgumentException("源区域超出边界");
}
if (dstposx + copyw > dstw || dstposy + copyh > dsth) {
throw new IllegalArgumentException("目标区域超出边界");
}
// 逐行拷贝(比逐像素快,利用内存连续性)
for (int row = 0; row < copyh; row++) {
int srcIdx = (srcposy + row) * srcw + srcposx;
int dstIdx = (dstposy + row) * dstw + dstposx;
System.arraycopy(src, srcIdx, dst, dstIdx, copyw);
}
}
}
+198
View File
@@ -0,0 +1,198 @@
package org.kne.codec.kif;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import jdk.internal.foreign.ArenaImpl;
import org.kne.membandboost.MembandBoost;
public class IntImageBlock {
private MemorySegment image; // int[] 的 MemorySegment 封装
private int width;
private int height;
private int posx;
private int posy;
public IntImageBlock(MemorySegment image, int width, int height, int posx, int posy) {
this.image = image;
this.width = width;
this.height = height;
this.posx = posx;
this.posy = posy;
}
public IntImageBlock(MemorySegment image, int width, int height) {
this.image = image;
this.width = width;
this.height = height;
this.posx = 0;
this.posy = 0;
}
public IntImageBlock(int width2, int height2, int posx2, int posy2) {
this(((ArenaImpl) Arena.ofAuto()).allocateNoInit((width2 * height2) << 2, 4), width2, height2, posx2, posy2);
}
public List<BlockCoord> getBlockCoords(int blockWidth, int blockHeight) {
return BlockCoord.calculateBlockCoords(width, height, blockWidth, blockHeight);
}
public IntImageBlock createBlock(BlockCoord coord) {
return IntImageBlock.fromImage(image, width, height, coord.getPosX(), coord.getPosY(), coord.getWidth(),
coord.getHeight());
}
public static IntImageBlock createBlock(BufferedImage image, BlockCoord coord) {
return IntImageBlock.fromImage(image, image.getWidth(), image.getHeight(), coord.getPosX(), coord.getPosY(),
coord.getWidth(), coord.getHeight());
}
// ==================== 从 MemorySegment 提取块 ====================
public static IntImageBlock fromImage(MemorySegment src, int srcw, int srch, int posx, int posy, int blockw,
int blockh) {
if (src == null) {
throw new IllegalArgumentException("源图像不能为空");
}
if (posx < 0 || posy < 0 || blockw <= 0 || blockh <= 0) {
throw new IllegalArgumentException("坐标和尺寸必须为正数");
}
if (posx + blockw > srcw || posy + blockh > srch) {
throw new IllegalArgumentException("块区域超出源图像边界");
}
MemorySegment blockPixels = ((ArenaImpl) Arena.ofAuto()).allocateNoInit((blockw * blockh) << 2, 4);
// 逐行拷贝(使用 MemorySegment.copy
for (int row = 0; row < blockh; row++) {
long srcOffset = ((long) (posy + row) * srcw + posx) << 2;
long dstOffset = (long) row * blockw << 2;
MemorySegment.copy(src, srcOffset, blockPixels, dstOffset, (long) blockw << 2);
}
return new IntImageBlock(blockPixels, blockw, blockh, posx, posy);
}
public static IntImageBlock fromImage(BufferedImage src, int srcw, int srch, int posx, int posy, int blockw,
int blockh) {
if (src == null) {
throw new IllegalArgumentException("源图像不能为空");
}
if (posx < 0 || posy < 0 || blockw <= 0 || blockh <= 0) {
throw new IllegalArgumentException("坐标和尺寸必须为正数");
}
if (posx + blockw > srcw || posy + blockh > srch) {
throw new IllegalArgumentException("块区域超出源图像边界");
}
int type = src.getType();
MemorySegment blockPixels = ((ArenaImpl) Arena.ofAuto()).allocateNoInit((long) blockw * blockh << 2, 4);
// 情况1TYPE_INT_ARGB 或 TYPE_INT_RGB → 零拷贝路径
if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) {
DataBufferInt buffer = (DataBufferInt) src.getRaster().getDataBuffer();
int[] pixels = buffer.getData();
for (int row = 0; row < blockh; row++) {
int srcIdx = (posy + row) * srcw + posx;
MemorySegment.copy(pixels, srcIdx, blockPixels, ValueLayout.JAVA_INT, (long) row * blockw << 2, blockw);
}
return new IntImageBlock(blockPixels, blockw, blockh, posx, posy);
}
// 情况2:其他格式 → 逐行读取,直接写入 MemorySegment
else {
int[] rowBuffer = new int[blockw];
for (int row = 0; row < blockh; row++) {
// 一次性读取一行(1 次 JNI 调用)
src.getRGB(posx, posy + row, blockw, 1, rowBuffer, 0, blockw);
// 直接写入 MemorySegment1 次 native 拷贝)
MemorySegment.copy(rowBuffer, 0, blockPixels, ValueLayout.JAVA_INT, (long) row * blockw << 2, blockw);
}
return new IntImageBlock(blockPixels, blockw, blockh, posx, posy);
}
}
// ==================== 写入目标图像 ====================
public void toImage(MemorySegment dst, int dstw, int dsth) {
if (dst == null) {
throw new IllegalArgumentException("目标图像不能为空");
}
if (posx < 0 || posy < 0) {
throw new IllegalArgumentException("块位置不能为负数");
}
if (posx + width > dstw || posy + height > dsth) {
throw new IllegalArgumentException("块区域超出目标图像边界");
}
// 逐行拷贝到目标图像
for (int row = 0; row < height; row++) {
long srcOffset = (long) row * width << 2;
long dstOffset = ((long) (posy + row) * dstw + posx) << 2;
MemorySegment.copy(image, srcOffset, dst, dstOffset, (long) width << 2);
}
}
public void toImage(MemorySegment dst, int dstw, int dsth, int dstPosX, int dstPosY) {
if (dst == null) {
throw new IllegalArgumentException("目标图像不能为空");
}
if (dstPosX < 0 || dstPosY < 0) {
throw new IllegalArgumentException("目标位置不能为负数");
}
if (dstPosX + width > dstw || dstPosY + height > dsth) {
throw new IllegalArgumentException("块区域超出目标图像边界");
}
for (int row = 0; row < height; row++) {
long srcOffset = (long) row * width << 2;
long dstOffset = ((long) (dstPosY + row) * dstw + dstPosX) << 2;
MemorySegment.copy(image, srcOffset, dst, dstOffset, (long) width << 2);
}
}
// ==================== 转换为 int[](兼容旧代码) ====================
public int[] toIntArray() {
int[] array = new int[width * height];
MemorySegment.copy(image, ValueLayout.JAVA_INT, 0, array, 0, width * height);
return array;
}
// ==================== Getter/Setter ====================
public MemorySegment getImage() {
return image;
}
public void setImage(MemorySegment image) {
this.image = image;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getPosx() {
return posx;
}
public int getPosy() {
return posy;
}
@Override
public String toString() {
return "IntImageBlock[" + width + "x" + height + ",(" + posx + "," + posy + ")]";
}
}
+471
View File
@@ -0,0 +1,471 @@
/**
* Zigzag 映射将有符号字节映射为非负字节
* 00, -11, 12, -23, 24, ...
*/
uchar zigzag_map(char x) {
int v = (int)x; // char 转为 int(保留符号)
return (uchar)((v >= 0) ? (v << 1) : ((-v << 1) - 1));
}
/**
* Zigzag 逆映射将非负字节还原为有符号字节
* 00, 1-1, 21, 3-2, 42, ...
*/
char zigzag_unmap(uchar y) {
int v = (int)y; // 转为无符号 0~255
return (char)((v & 1) == 0 ? (v >> 1) : (-((v + 1) >> 1)));
}
/**
* 像素安全加法RGBA 四个通道分别相加结果裁剪到 0~255
*/
int pixelAdd(int a, int b) {
int ar = (a >> 16) & 0xFF;
int ag = (a >> 8) & 0xFF;
int ab = a & 0xFF;
int aa = (a >> 24) & 0xFF;
int br = (b >> 16) & 0xFF;
int bg = (b >> 8) & 0xFF;
int bb = b & 0xFF;
int ba = (b >> 24) & 0xFF;
int r = ar + br;
int g = ag + bg;
int bv = ab + bb;
int av = aa + ba;
// &0xFF 替代 min/max保留低 8 自动溢出等同于裁剪
return ((av & 0xFF) << 24) |
((r & 0xFF) << 16) |
((g & 0xFF) << 8) |
(bv & 0xFF);
}
/**
* 像素安全减法(等效于 Java 的 pixelSub
* 逐通道相减,结果裁剪到 0~255,组装回 ARGB
*/
int pixelSub(int a, int b) {
int ar = (a >> 16) & 0xFF;
int ag = (a >> 8) & 0xFF;
int ab = a & 0xFF;
int aa = (a >> 24) & 0xFF;
int br = (b >> 16) & 0xFF;
int bg = (b >> 8) & 0xFF;
int bb = b & 0xFF;
int ba = (b >> 24) & 0xFF;
int r = ar - br;
int g = ag - bg;
int bv = ab - bb;
int av = aa - ba;
return ((av & 0xFF) << 24) |
((r & 0xFF) << 16) |
((g & 0xFF) << 8) |
(bv & 0xFF);
}
/**
* 位平面编码矩阵转置风格- 等效于 Java 版本
*
* 核心逻辑
* 对于输出数组的每个字节依次从输入数组的8个位平面各取1个bit
* 输出字节的 bit i = 输入数组第 i 个位平面的当前位
*
* 输出数组的每个字节由8个位平面的各1个bit组成
* 等价于 8×len bit 矩阵转置为 len×8 bit 矩阵
*/
__kernel void encodePacked(
__global const uchar* input, // 输入字节数组
__global uchar* output, // 输出字节数组长度与输入相同
int len // 输入/输出长度
) {
int outIdx = get_global_id(0);
if (outIdx >= len) return;
int outByte = 0;
// 从8个位平面各取1个bit组装成一个字节
for (int plane = 0; plane < 8; plane++) {
int bitpos = (outIdx << 3) + plane;
int inputPos = bitpos % len;
int bitShift = bitpos / len;
int bit = (input[inputPos] >> (7 - bitShift)) & 1;
outByte |= (bit << plane);
}
output[outIdx] = (uchar)outByte;
}
__kernel void decodePacked(__global const unsigned char* datain,
__global unsigned char* dataout,
int size) {
int outIdx = get_global_id(0);
// 边界检查
if (outIdx >= size) {
return;
}
int outByte = 0;
// 从 8 个位平面各取 1 个 bit,组装成一个字节
// plane 0 → bit7, plane 1 → bit6, ..., plane 7 → bit0
for (int plane = 0; plane < 8; plane++) {
int bitpos = plane * size + outIdx;
int inputpos = bitpos >> 3;
int inputshift = bitpos & 0b111;
int bit = (datain[inputpos] >> inputshift) & 1;
outByte |= (bit << (7 - plane));
}
dataout[outIdx] = (unsigned char)outByte;
}
/**
* 序列化 + Zigzag 映射RGBA 交错
*/
__kernel void serializeRGBAWithZigzag(
__global int* pixels,
__global uchar* output,
int size
) {
int i = get_global_id(0);
if (i >= size) return;
int pixel = pixels[i];
char r = (char)((pixel >> 16) & 0xFF);
char g = (char)((pixel >> 8) & 0xFF);
char b = (char)(pixel & 0xFF);
char a = (char)((pixel >> 24) & 0xFF);
int idx = i * 4;
output[idx + 0] = zigzag_map(r);
output[idx + 1] = zigzag_map(g);
output[idx + 2] = zigzag_map(b);
output[idx + 3] = zigzag_map(a);
}
// 对应 deserializeRGBAWithZigzag
__kernel void deserializeRGBAWithZigzag(
__global const uchar* data, // 输入Zigzag 映射后的字节流RGBA 交错
__global int* pixels, // 输出像素数组ARGB
int size // 像素数量 (w * h)
) {
int i = get_global_id(0);
if (i >= size) return;
int idx = i * 4;
// 读取并逆映射四个通道
uchar r_raw = data[idx];
uchar g_raw = data[idx + 1];
uchar b_raw = data[idx + 2];
uchar a_raw = data[idx + 3];
char r = zigzag_unmap(r_raw);
char g = zigzag_unmap(g_raw);
char b = zigzag_unmap(b_raw);
char a = zigzag_unmap(a_raw);
// 组装成 ARGB (Java int 格式)
// 注意OpenCL int 32 位有符号 Java 一致
pixels[i] = ((int)(a & 0xFF) << 24) |
((int)(r & 0xFF) << 16) |
((int)(g & 0xFF) << 8) |
(int)(b & 0xFF);
}
/**
* 序列化 + Zigzag 映射(平面 RGBA
*/
__kernel void serializePlannarRGBAWithZigzag(
__global int* pixels,
__global uchar* output,
int size
) {
int i = get_global_id(0);
if (i >= size) return;
int pixel = pixels[i];
char r = (char)((pixel >> 16) & 0xFF);
char g = (char)((pixel >> 8) & 0xFF);
char b = (char)(pixel & 0xFF);
char a = (char)((pixel >> 24) & 0xFF);
int rOffset = 0;
int gOffset = size;
int bOffset = size * 2;
int aOffset = size * 3;
output[rOffset + i] = zigzag_map(r);
output[gOffset + i] = zigzag_map(g);
output[bOffset + i] = zigzag_map(b);
output[aOffset + i] = zigzag_map(a);
}
// 对应 deserializePlannarRGBAWithZigzag
__kernel void deserializePlannarRGBAWithZigzag(
__global const uchar* data, // 输入:Zigzag 映射后的字节流(RRRR...GGGG...BBBB...AAAA...
__global int* pixels, // 输出:像素数组(ARGB)
int size // 像素数量 (w * h)
) {
int i = get_global_id(0);
if (i >= size) return;
int rOffset = 0;
int gOffset = size;
int bOffset = size * 2;
int aOffset = size * 3;
// 从四个平面分别读取并逆映射
uchar r_raw = data[rOffset + i];
uchar g_raw = data[gOffset + i];
uchar b_raw = data[bOffset + i];
uchar a_raw = data[aOffset + i];
char r = zigzag_unmap(r_raw);
char g = zigzag_unmap(g_raw);
char b = zigzag_unmap(b_raw);
char a = zigzag_unmap(a_raw);
// 组装成 ARGB
pixels[i] = ((int)(a & 0xFF) << 24) |
((int)(r & 0xFF) << 16) |
((int)(g & 0xFF) << 8) |
(int)(b & 0xFF);
}
/**
* 水平预测非原地完美并行
* residual = pixelSub(current, left)
* 每个线程独立处理一个像素
*/
__kernel void horizontalPredictParallel(
__global const int* input, // 原始像素
__global int* output, // 残差输出
int w,
int h
) {
int idx = get_global_id(0);
int size = w * h;
if (idx >= size) return;
int x = idx % w;
if (x == 0) {
// 第一列残差 = 原值没有左邻居
output[idx] = input[idx];
} else {
int left = input[idx - 1];
output[idx] = pixelSub(input[idx], left);
}
}
/**
* 垂直预测非原地完美并行
* residual = pixelSub(current, above)
* 每个线程独立处理一个像素
*/
__kernel void verticalPredictParallel(
__global const int* input, // 原始像素或水平预测后的残差
__global int* output, // 残差输出
int w,
int h
) {
int idx = get_global_id(0);
int size = w * h;
if (idx >= size) return;
int y = idx / w;
if (y == 0) {
// 第一行残差 = 原值没有上邻居
output[idx] = input[idx];
} else {
int above = input[idx - w];
output[idx] = pixelSub(input[idx], above);
}
}
/**
* 水平预测使用 pixelSub
* residual = pixelSub(current, left)
* 从右向左保证 left 是原始值
*/
__kernel void horizontalPredictInPlace(
__global int* data,
int w,
int h
) {
int row = get_global_id(0);
if (row >= h) return;
int base = row * w;
// 从右向左第一列不变
for (int x = w - 1; x >= 1; x--) {
int idx = base + x;
int left = data[idx - 1];
data[idx] = pixelSub(data[idx], left);
}
}
/**
* 水平逆预测原地操作
* 每个工作组处理一行行内从左到右串行
*
* 数据依赖左侧像素 (x-1, y) 必须先被还原
*//**
* 水平逆预测寄存器优化版
*
* 核心优化预测值在寄存器中传递减少显存读取
*/
__kernel void horizontalInverseInPlace(
__global int* data,
int w,
int h
) {
int row = get_global_id(0);
if (row >= h) return;
int base = row * w;
// 第一列保持不变
// 直接用 data[base] 作为初始预测值
int pred = data[base]; // ✅ 只读一次显存
// 行内从左到右串行 pred 在寄存器中传递
for (int x = 1; x < w; x++) {
int idx = base + x;
int residual = data[idx]; // ✅ 只读残差
int result = pixelAdd(pred, residual);
data[idx] = result; // ✅ 只写一次显存
pred = result; // ✅ 寄存器传递(下次循环直接使用)
}
}
/**
* 垂直预测使用 pixelSub
* residual = pixelSub(current, above)
* 从下向上保证 above 是原始值
*/
__kernel void verticalPredictInPlace(
__global int* data,
int w,
int h
) {
int col = get_global_id(0);
if (col >= w) return;
// 从下向上第一行不变
for (int y = h - 1; y >= 1; y--) {
int idx = y * w + col;
int above = data[(y - 1) * w + col];
data[idx] = pixelSub(data[idx], above);
}
}
/**
* 垂直逆预测原地操作
* 每个工作组处理一列列内从上到下串行
*
* 数据依赖上方像素 (x, y-1) 必须先被还原
*//**
* 垂直逆预测寄存器优化版
*/
__kernel void verticalInverseInPlace(
__global int* data,
int w,
int h
) {
int col = get_global_id(0);
if (col >= w) return;
// 第一行 data[col] 作为初始预测值
int pred = data[col]; // ✅ 只读一次显存
for (int y = 1; y < h; y++) {
int idx = y * w + col;
int residual = data[idx]; // ✅ 只读残差
int result = pixelAdd(pred, residual);
data[idx] = result;
pred = result; // ✅ 寄存器传递
}
}
/**
* RGBA 颜色变换原地版本- 等效于 Java colorTransformRGBAInPlace
*
* 编码R' = R - G, G' = G, B' = B - G, A' = A
* 结果存储为A | R' | G | B'(与 Java 版本完全一致)
*/
__kernel void colorTransformRGBAInPlace(
__global int* data, // 输入/输出像素数组
int size // 像素总数 (w * h)
) {
int idx = get_global_id(0);
if (idx >= size) return;
int pixel = data[idx];
int r = (pixel >> 16) & 0xFF;
int g = (pixel >> 8) & 0xFF;
int b = pixel & 0xFF;
int a = (pixel >> 24) & 0xFF;
int rg = r - g; // 范围 -255 ~ 255
int bg = b - g; // 范围 -255 ~ 255
// 与 Java 版本完全一致:
// ((a & 0xFF) << 24) | ((rg & 0xFF) << 16) | ((g & 0xFF) << 8) | (bg & 0xFF)
data[idx] = ((a & 0xFF) << 24) |
((rg & 0xFF) << 16) |
((g & 0xFF) << 8) |
(bg & 0xFF);
}
/**
* 颜色逆变换(RGBA,逐像素并行)- 与 colorTransformRGBAInPlace 完全对应
*
* Java 版本:
* a = (packed >> 24) & 0xFF
* rg = (packed >> 16) & 0xFF
* g = (packed >> 8) & 0xFF
* bg = packed & 0xFF
* r = g + rg
* b = g + bg
* result = (a << 24) | (r << 16) | (g << 8) | b
*/
__kernel void colorInverseRGBAInPlace(
__global int* data, // 输入颜色差分数据输出还原后的 RGBA
int size // 像素总数 (w * h)
) {
int idx = get_global_id(0);
if (idx >= size) return;
int packed = data[idx];
// 提取各通道
int a = (packed >> 24) & 0xFF;
int rg = (packed >> 16) & 0xFF;
int g = (packed >> 8) & 0xFF;
int bg = packed & 0xFF;
// 还原 R B
int r = g + rg;
int b = g + bg;
// 裁剪到 0~255 &0xFF 保留低 8
r = r & 0xFF;
b = b & 0xFF;
// 组装回 ARGB
data[idx] = ((a & 0xFF) << 24) |
((r & 0xFF) << 16) |
((g & 0xFF) << 8) |
(b & 0xFF);
}
+192
View File
@@ -0,0 +1,192 @@
package org.kne.codec.kif;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.imageio.stream.ImageInputStream;
import org.kne.debug.TimeDebugger;
import org.kne.opencl64.OpenCLDevice;
import org.kne.opencl64.concurrent.OpenCLExecutors;
public class KIFCodec {
private static ThreadPoolExecutor cpupool ;
private static ThreadPoolExecutor gpupool ;
static {
cpupool= (ThreadPoolExecutor) Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(),
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "KIFCodec-CPU-Worker-" + threadNumber.getAndIncrement());
t.setDaemon(true); // 设置为守护线程
return t;
}
}
);
try {
List<OpenCLDevice>devs=OpenCLDevice.getAllGPU();
for(OpenCLDevice dev:devs) {
System.out.println(dev);
}
gpupool=(ThreadPoolExecutor) OpenCLExecutors.newFixedThreadPool(devs,4);
}catch(Throwable e) {
e.printStackTrace();
}
}
public static KIFCompressedImageBlock encode(IntImageBlock input,KIFCompressMode mode) {
KIFEncodePipeline kip=new KIFEncodePipeline(input, mode);
kip.encodeStage1();
return kip.encodeStage2();
}
public static Future<KIFCompressedImageBlock> encodeAsync(IntImageBlock input,KIFCompressMode mode) {
return encodeAsync(()->{return input;},mode);
}
public static Future<KIFCompressedImageBlock> encodeAsync(Supplier<IntImageBlock> input,KIFCompressMode mode) {
/*CompletableFuture<KIFEncodePipeline> f1=CompletableFuture.supplyAsync(()->{
KIFEncodePipeline kip=new KIFEncodePipeline(input, mode);
kip.encodeStage1();
return kip;
}, gpupool);
CompletableFuture<KIFCompressedImageBlock>f2=f1.thenApplyAsync((pipeline)->{
return pipeline.encodeStage2();
},cpupool);*/
return getBestPool().submit(()->{
KIFEncodePipeline kip=new KIFEncodePipeline(input.get(), mode);
kip.encodeStage1();
return kip.encodeStage2();
});
}
public static Future<KIFCompressedImageBlock> encodeAsyncMultimode(IntImageBlock block, List<KIFCompressMode> list) {
return encodeAsyncMultimode(()->{return block;},list);
}
public static Future<KIFCompressedImageBlock> encodeAsyncMultimode(Supplier< IntImageBlock> block, List<KIFCompressMode> list) {
// 为每个 mode 提交一个异步编码任务
@SuppressWarnings("unchecked")
ArrayList<Future<KIFCompressedImageBlock>> futures = new ArrayList<Future<KIFCompressedImageBlock>>(list.size());
for (KIFCompressMode mode:list) {
// 这里假设 encodeAsync 是提交到线程池的方法
futures.add( encodeAsync(block, mode));
}
// 返回 MultiModeFuture,它会等待所有任务完成,选择最小的
return new MultiModeFuture(futures);
}
public static IntImageBlock decode( KIFCompressedImageBlock input) throws IOException {
KIFDecodePipeline kdec=new KIFDecodePipeline(input);
return kdec.decodeStage2();
}
public static Future<IntImageBlock> decodeAsync(Supplier< KIFCompressedImageBlock> input) {
return decodeAndConsumeAsync(input,null);
}
public static Future<IntImageBlock> decodeAndConsumeAsync(Supplier< KIFCompressedImageBlock> input,Consumer<IntImageBlock> con) {
/*CompletableFuture<KIFDecodePipeline> f1=CompletableFuture.supplyAsync(()->{
KIFDecodePipeline kip=new KIFDecodePipeline(input);
try {
kip.decodeStage1();
} catch (IOException e) {
throw new RuntimeException(e);
}
return kip;
}, cpupool);
CompletableFuture<IntImageBlock>f2=f1.thenApplyAsync((pipeline)->{
try {
return pipeline.decodeStage2();
} catch (IOException e) {
throw new RuntimeException(e);
}
},gpupool);*/
return getBestPool().submit(()->{
KIFDecodePipeline kip=new KIFDecodePipeline(input.get());
try {
kip.decodeStage1();
} catch (IOException e) {
throw new RuntimeException(e);
}
IntImageBlock out=kip.decodeStage2();
if(con!=null)
con.accept(out);
return out;
});
}
public static void gpuBackPressure() {
while(isGPUOverloaded()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void cpuBackPressure() {
while(isCPUOverloaded()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 判断是否过载(调用方可据此决定是否降级处理)
*/
public static boolean isGPUOverloaded() {
return gpupool.getQueue().size() > gpupool.getPoolSize() ;
}
/**
* 判断是否过载(调用方可据此决定是否降级处理)
*/
public static boolean isCPUOverloaded() {
return cpupool.getQueue().size() > cpupool.getPoolSize() ;
}
public static void backPressure() {
while(isCPUOverloaded()&&isGPUOverloaded()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static AbstractExecutorService getBestPool() {
if(gpupool==null) {
return cpupool;
}
if(isGPUOverloaded()) {
return cpupool;
}else {
return gpupool;
}
}
}
+190
View File
@@ -0,0 +1,190 @@
package org.kne.codec.kif;
import java.util.ArrayList;
import java.util.List;
public class KIFCompressMode {
private int mode;
public KIFCompressMode(int mode) {
super();
this.mode = mode;
}
public int getMode() {
return mode;
}
// ==================== 6个bit的getter方法 ====================
/**
* 是否启用水平预测 (H)
*/
public boolean isH() {
return (mode & 0b0001) != 0;
}
/**
* 是否启用垂直预测 (V)
*/
public boolean isV() {
return (mode & 0b0010) != 0;
}
/**
* 是否启用左上角预测 (TL)
*/
public boolean isTL() {
return (mode & 0b0100) != 0;
}
/**
* 是否启用右上角预测 (TR)
*/
public boolean isTR() {
return (mode & 0b1000) != 0;
}
/**
* 是否启用颜色差分 (CD)
*/
public boolean isCD() {
return (mode & 0b10000) != 0;
}
/**
* 是否启用位平面 (BP)
*/
public boolean isBP() {
return (mode & 0b100000) != 0;
}
/**
* 是否启用平面序列化 (PL)
* 启用后,像素按 RRRGGGBBB 顺序存储,而非 RGBRGBRGB
* 对屏幕内容可能有更好的压缩率
*/
public boolean isPL() {
return (mode & 0b1000000) != 0;
}
// ==================== 批量判断 ====================
/**
* 检查是否启用了任何预测器 (H/V/TL/TR)
*/
public boolean hasAnyPredictor() {
return (mode & 0b1111) != 0;
}
/**
* 获取预测器部分(低4位)
*/
public int getPredictorBits() {
return mode & 0b1111;
}
/**
* 获取当前启用的预测器数量
*/
public int getPredictorCount() {
int count = 0;
int n = mode & 0b1111;
while (n != 0) {
count++;
n &= (n - 1);
}
return count;
}
public int getEnableCount() {
return bitcount(mode&0xff);
}
private static int bitcount ( int n)
{
int count=0 ;
while (n!=0) {
count++ ;
n &= (n - 1) ;
}
return count ;
}
// ==================== 原有的hashCode/equals/toString ====================
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mode;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
KIFCompressMode other = (KIFCompressMode) obj;
if (mode != other.mode)
return false;
return true;
}
/**
* 将模式编号转换为可读的预测器组合字符串
*/
private static String modeToString(int mode) {
StringBuilder sb = new StringBuilder();
if ((mode & 0b0001) != 0) sb.append("H ");
if ((mode & 0b0010) != 0) sb.append("V ");
if ((mode & 0b0100) != 0) sb.append("TL ");
if ((mode & 0b1000) != 0) sb.append("TR ");
if ((mode & 0b10000) != 0) sb.append("CD ");
if ((mode & 0b100000) != 0) sb.append("BP ");
if ((mode & 0b1000000) != 0) sb.append("PL ");
if (sb.length() == 0) sb.append("0 ");
return sb.toString().trim();
}
@Override
public String toString() {
return modeToString(mode) + "(" + mode + ")";
}
public static List<KIFCompressMode> getAllModes() {
List<KIFCompressMode> result=new ArrayList<>(64);
for(int i=0;i<128;i++) {
KIFCompressMode mode=new KIFCompressMode(i);
if(mode.isTL()||mode.isTR()) {
continue;
}
result.add(mode);
}
return result;
}public static List<KIFCompressMode> getNaturalImageModes() {
List<KIFCompressMode> result = new ArrayList<>(16);
// 自然图像中最常用的模式(按优先级排序)
result.add(new KIFCompressMode(115)); // H V CD BP PL(115)
result.add(new KIFCompressMode(114)); // V CD BP PL(114)
result.add(new KIFCompressMode(113)); // H CD BP PL(113)
return result;
}
public static List<KIFCompressMode> getScreenImageModes() {
List<KIFCompressMode> result = new ArrayList<>(16);
// 屏幕图像中最常用的模式(按优先级排序)
result.add(new KIFCompressMode(0));//0(0)
result.add(new KIFCompressMode(114));// V CD BP PL(114)
result.add(new KIFCompressMode(16)); // CD(16)
result.add(new KIFCompressMode(80)); // CD PL(80)
result.add(new KIFCompressMode(17)); // H CD(17)
result.add(new KIFCompressMode(34)); // V BP(34)
result.add(new KIFCompressMode(115)); // H V CD BP PL(115)
return result;
}
}
@@ -0,0 +1,207 @@
package org.kne.codec.kif;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
import org.kne.membandboost.MembandBoost;
public class KIFCompressedImageBlock implements Comparable<KIFCompressedImageBlock>{
private byte[] data;
private int sizeBeforeCompress;
private int width;
private int height;
private int posx;
private int posy;
private KIFCompressMode predictMode; // 预测模式 (0=H+V, 1=TL+TR, 等)
public KIFCompressedImageBlock(byte[] data, int sizeBeforeCompress, int width, int height,
int posx, int posy, KIFCompressMode predictMode) {
super();
this.data = data;
this.sizeBeforeCompress = sizeBeforeCompress;
this.width = width;
this.height = height;
this.posx = posx;
this.posy = posy;
this.predictMode = predictMode;
}
// ==================== I/O 方法 ====================
/**
* 从 DataInput 读取一个压缩块
* 格式:
* [predictMode 1B] [预留 3B]
* [width 4B] [height 4B] [posx 4B] [posy 4B]
* [sizeBeforeCompress 4B] [dataLength 4B] [data N B]
*
* @param in DataInput 源
* @return 读取到的 KIFCompressedImageBlock
* @throws IOException 如果读取失败
*/
public static KIFCompressedImageBlock readFromStream(DataInput in) throws IOException {
// 1. 读取预测模式
byte predictMode = in.readByte();
// 2. 跳过预留字节 (3 bytes)
in.skipBytes(1);
// 3. 读取元数据
int width = in.readInt();
int height = in.readInt();
int posx = in.readInt();
int posy = in.readInt();
int sizeBeforeCompress = in.readInt();
int dataLength = in.readInt();
// 4. 读取数据
byte[] data = MembandBoost.allocateUninitializedByteArray(dataLength);
in.readFully(data);
return new KIFCompressedImageBlock(data, sizeBeforeCompress, width, height, posx, posy,new KIFCompressMode( predictMode));
}
/**
* 将当前块写入 DataOutput
*
* @param out DataOutput 目标
* @throws IOException 如果写入失败
*/
public void writeToStream(DataOutput out) throws IOException {
// 1. 写入预测模式
out.writeByte(predictMode.getMode());
// 2. 预留字节 (1 bytes)
out.writeByte(0);
// 3. 写入元数据
out.writeInt(width);
out.writeInt(height);
out.writeInt(posx);
out.writeInt(posy);
out.writeInt(sizeBeforeCompress);
out.writeInt(data.length);
// 4. 写入数据
out.write(data);
}
/**
* 计算写入流时需要的字节数
*/
public int getSerializedSize() {
return 1 + 3 + 6 * 4 + data.length;
}
// ==================== 静态工厂方法 ====================
public static KIFCompressedImageBlock fromIntImageBlock(IntImageBlock block, byte[] compressedData,
int sizeBeforeCompress, KIFCompressMode predictMode) {
return new KIFCompressedImageBlock(
compressedData,
sizeBeforeCompress,
block.getWidth(),
block.getHeight(),
block.getPosx(),
block.getPosy(),
predictMode
);
}
// ==================== Getters/Setters ====================
public byte[] getData() {
return data;
}
public int getSizeBeforeCompress() {
return sizeBeforeCompress;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int getPosx() {
return posx;
}
public int getPosy() {
return posy;
}
public KIFCompressMode getPredictMode() {
return predictMode;
}
// ==================== toString / hashCode / equals ====================
@Override
public String toString() {
return "KIFCompressedImageBlock[" + width + "x" + height + ",(" + posx + "," + posy + "),mode=" + predictMode + "," + data.length + "B,src=" + sizeBeforeCompress + "B]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(data);
result = prime * result + height;
result = prime * result + posx;
result = prime * result + posy;
result = prime * result + ((predictMode == null) ? 0 : predictMode.hashCode());
result = prime * result + sizeBeforeCompress;
result = prime * result + width;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
KIFCompressedImageBlock other = (KIFCompressedImageBlock) obj;
if (!Arrays.equals(data, other.data))
return false;
if (height != other.height)
return false;
if (posx != other.posx)
return false;
if (posy != other.posy)
return false;
if (predictMode == null) {
if (other.predictMode != null)
return false;
} else if (!predictMode.equals(other.predictMode))
return false;
if (sizeBeforeCompress != other.sizeBeforeCompress)
return false;
if (width != other.width)
return false;
return true;
}
@Override
public int compareTo(KIFCompressedImageBlock o) {
int v=Integer.compare(data.length, o.data.length);
if(v==0) {
return Integer.compare(predictMode.getEnableCount(), o.predictMode.getEnableCount()) ;
}else {
return v;
}
}
}
@@ -0,0 +1,178 @@
package org.kne.codec.kif;
import java.awt.geom.Area;
import java.io.IOException;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import org.kne.debug.TimeDebugger;
import org.kne.membandboost.MembandBoost;
import org.kne.opencl64.OpenCLKernel;
import org.kne.opencl64.OpenCLMemory;
import org.kne.opencl64.OpenCLMemoryCopyType;
import org.kne.opencl64.OpenCLProgram;
import org.kne.opencl64.concurrent.OpenCLContextThread;
import org.kne.opencl64.concurrent.ThreadLocalOpenCLMemory;
import jdk.internal.foreign.ArenaImpl;
public class KIFDecodePipeline {
private KIFCompressedImageBlock input;
public KIFDecodePipeline(KIFCompressedImageBlock input) {
super();
this.input = input;
data = input.getData();
width = input.getWidth();
height = input.getHeight();
mode = input.getPredictMode();
}
private TimeDebugger tdb=new TimeDebugger("解码时间",false);
private volatile byte[] data;
private volatile int width;
private volatile int height;
private volatile KIFCompressMode mode;
private volatile byte[] dataserp;
public void decodeStage1() throws IOException {
dataserp = Compressors.decompressZstd(data,input.getSizeBeforeCompress());
tdb.mark("熵解码");
}
public IntImageBlock decodeStage2() throws IOException {
Thread t =Thread. currentThread();
if (t instanceof OpenCLContextThread) {
OpenCLContextThread oct = (OpenCLContextThread) t;
return decodeStage2GPU(oct);
}else {
return decodeStage2CPU();
}
}
private static ThreadLocal<MemorySegment>m1=ThreadLocal.withInitial(()->{
return ((ArenaImpl)Arena.ofAuto()).allocateNoInit(1024*1024*4,4);
});
private static ThreadLocalOpenCLMemory tlm0=new ThreadLocalOpenCLMemory(1024*1024*4);
private static ThreadLocalOpenCLMemory tlm1=new ThreadLocalOpenCLMemory(1024*1024*4);
private static ThreadLocalOpenCLMemory tlm2=new ThreadLocalOpenCLMemory(1024*1024*4);
private IntImageBlock decodeStage2GPU(OpenCLContextThread oct) throws IOException {
tdb.mark("等待调度");
OpenCLProgram prog = oct.getProgram("/org/kne/codec/kif/KIFCodec.cl");
tdb.mark("加载程序");
//OpenCLKernel krl4 = prog.createKernel("colorInverseRGBAInPlace");
OpenCLMemory ocm1 = null;
OpenCLMemory ocm2 = null;
OpenCLMemory ocm3 = null;
int[]des;
MemorySegment dataserpseg= m1.get();
tdb.mark("堆外内存分配");
dataserpseg.asByteBuffer().put(dataserp).flip();
tdb.mark("堆内存->堆外内存");
ocm1=tlm0.get(oct.getOpenCLContext());
tdb.mark("分配显存");
oct.getOpenCLCommandQueue().executeWriteBuffer(ocm1, dataserpseg, false,0,0,dataserp.length);
tdb.mark("内存->显存");
OpenCLMemory dataser;
if(mode.isBP()) {
ocm2=tlm1.get(oct.getOpenCLContext());
OpenCLKernel krl0 = prog.createKernel("decodePacked");
krl0.putArg(ocm1);
krl0.putArg(ocm2);
krl0.putArg(dataserp.length);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl0, 0, dataserp.length);
dataser=ocm2;//
}else {
dataser=ocm1;
}
// oct.getOpenCLCommandQueue().finish();
// tdb.mark("位平面解码");
ocm3=tlm2.get(oct.getOpenCLContext());
if(mode.isPL()) {
OpenCLKernel krl1 = prog.createKernel("deserializePlannarRGBAWithZigzag");
krl1.putArg(dataser);
krl1.putArg(ocm3);
krl1.putArg(width*height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl1, 0,width*height);
}else {
OpenCLKernel krl2 = prog.createKernel("deserializeRGBAWithZigzag");
krl2.putArg(dataser);
krl2.putArg(ocm3);
krl2.putArg(width*height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl2, 0,width*height);
}
// oct.getOpenCLCommandQueue().finish();
// tdb.mark("反序列化");
if(mode.isCD()) {
OpenCLKernel krl4 = prog.createKernel("colorInverseRGBAInPlace");
krl4.putArg(ocm3);
krl4.putArg(width*height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl4, 0, width*height);
}
if(mode.isV()) {
OpenCLKernel krl3 = prog.createKernel("verticalInverseInPlace");
krl3.putArg(ocm3);
krl3.putArg(width);
krl3.putArg(height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl3, 0, width);
}
if(mode.isH()) {
OpenCLKernel krl3 = prog.createKernel("horizontalInverseInPlace");
krl3.putArg(ocm3);
krl3.putArg(width);
krl3.putArg(height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl3, 0, height);
}
// oct.getOpenCLCommandQueue().finish();
// tdb.mark("预测解码");
tdb.mark("解码");
IntImageBlock result=new IntImageBlock( width, height, input.getPosx(), input.getPosy());
tdb.mark("分配堆外内存");
oct.getOpenCLCommandQueue().executeReadBuffer(ocm3, result.getImage(), true,0,0,width*height*4);
tdb.mark("显存->内存");
// MemorySegment.copy(dataserpseg, ValueLayout.JAVA_INT, 0, des, 0, des.length);
// tdb.mark("堆外内存->堆内存");
tdb.print();
return result;
}
private IntImageBlock decodeStage2CPU() throws IOException {
byte[]dataser;
if(mode.isBP()) {
dataser=BitPlane.decodePacked(dataserp);
}else {
dataser=dataserp;
}
tdb.mark("位平面解码");
int[]des;
if(mode.isPL()) {
des=PixelSerializer.deserializePlannarRGBAWithZigzag(dataser, width, height);
}else {
des=PixelSerializer.deserializeRGBAWithZigzag(dataser, width, height);
}
tdb.mark("反序列化");
if(mode.isTR())
Predictor.inverseInPlace(des, width, height,1,-1);
if(mode.isTL())
Predictor.inverseInPlace(des, width, height,-1,-1);
if(mode.isV())
Predictor.inverseInPlace(des, width, height,0,-1);
if(mode.isH())
Predictor.inverseInPlace(des, width, height,-1,0);
if(mode.isCD())
ColorTransform.colorInverseRGBAInPlace(des, width, height);
tdb.mark("预测解码");
tdb.print();
return new IntImageBlock(MemorySegment.ofArray(des), width, height, input.getPosx(), input.getPosy());
}
}
@@ -0,0 +1,186 @@
package org.kne.codec.kif;
import jdk.internal.foreign.ArenaImpl;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.ValueLayout.OfInt;
import org.kne.debug.TimeDebugger;
import org.kne.membandboost.MembandBoost;
import org.kne.opencl64.OpenCLKernel;
import org.kne.opencl64.OpenCLMemory;
import org.kne.opencl64.OpenCLProgram;
import org.kne.opencl64.concurrent.OpenCLContextThread;
import org.kne.opencl64.concurrent.ThreadLocalOpenCLMemory;
public class KIFEncodePipeline {
private volatile IntImageBlock input;
private volatile KIFCompressMode mode;
private volatile TimeDebugger tdb=new TimeDebugger("编码时间",false);
public KIFEncodePipeline(IntImageBlock input, KIFCompressMode mode) {
super();
this.input = input;
this.mode = mode;
pixels = input.getImage();
width = input.getWidth();
height = input.getHeight();
}
private byte[] serialData2;
private MemorySegment pixels;
private int width;
private int height;
public void encodeStage1() {
Thread t =Thread. currentThread();
if (t instanceof OpenCLContextThread) {
OpenCLContextThread oct = (OpenCLContextThread) t;
encodeStage1GPU(oct);
}else {
encodeStage1CPU();
}
}
private void encodeStage1CPU() {
int[]pixels2=pixels.toArray(OfInt.JAVA_INT);
if(mode.isCD()) {
pixels2=ColorTransform.colorTransformRGBA(pixels2, width, height);
}else {
pixels2=pixels2.clone();
}
if(mode.isH())
Predictor.predictInPlace(pixels2, width, height,-1,0);
if(mode.isV())
Predictor.predictInPlace(pixels2, width, height,0,-1);
if(mode.isTL())
Predictor.predictInPlace(pixels2, width, height,-1,-1);
if(mode.isTR())
Predictor.predictInPlace(pixels2, width, height,1,-1);
tdb.mark("预测编码");
byte[] serialData ;
if(mode.isPL()) {
serialData= PixelSerializer.serializePlannarRGBAWithZigzag(pixels2, width, height);
}else {
serialData= PixelSerializer.serializeRGBAWithZigzag(pixels2, width, height);
}
tdb.mark("序列化");
if(mode.isBP()) {
serialData2= BitPlane.encodePacked(serialData);
}else {
serialData2=serialData;
}
tdb.mark("位平面编码");
}
private static ThreadLocal<MemorySegment>m1=ThreadLocal.withInitial(()->{
return ((ArenaImpl)Arena.ofAuto()).allocateNoInit(1024*1024*4,4);
});
private static ThreadLocalOpenCLMemory tlm0=new ThreadLocalOpenCLMemory(1024*1024*4);
private static ThreadLocalOpenCLMemory tlmpred1=new ThreadLocalOpenCLMemory(1024*1024*4);
private static ThreadLocalOpenCLMemory tlmpred2=new ThreadLocalOpenCLMemory(1024*1024*4);
private static ThreadLocalOpenCLMemory tlm1=new ThreadLocalOpenCLMemory(1024*1024*4);
private static ThreadLocalOpenCLMemory tlm2=new ThreadLocalOpenCLMemory(1024*1024*4);
private void encodeStage1GPU(OpenCLContextThread oct) {
OpenCLProgram prog = oct.getProgram("/org/kne/codec/kif/KIFCodec.cl");
tdb.mark("加载程序");
//OpenCLKernel krl4 = prog.createKernel("colorInverseRGBAInPlace");
OpenCLMemory ocm1 = null;
OpenCLMemory ocm2 = null;
OpenCLMemory ocm3 = null;
MemorySegment dataserpseg= m1.get();
tdb.mark("堆外内存分配");
//MemorySegment.copy(pixels,0,dataserpseg,0, pixels.byteSize());
tdb.mark("堆内存->堆外内存");
ocm1=tlm0.get(oct.getOpenCLContext());
tdb.mark("分配显存");
oct.getOpenCLCommandQueue().executeWriteBuffer(ocm1, pixels, false,0,0,pixels.byteSize());
tdb.mark("内存->显存");
if(mode.isH()) {
OpenCLKernel krl3 = prog.createKernel("horizontalPredictParallel");
OpenCLMemory opred1=tlmpred1.get(oct.getOpenCLContext());
krl3.putArg(ocm1);
krl3.putArg(opred1);
krl3.putArg(width);
krl3.putArg(height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl3, 0, width*height);
ocm1=opred1;
}
if(mode.isV()) {
OpenCLKernel krl3 = prog.createKernel("verticalPredictParallel");
OpenCLMemory opred2=tlmpred2.get(oct.getOpenCLContext());
krl3.putArg(ocm1);
krl3.putArg(opred2);
krl3.putArg(width);
krl3.putArg(height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl3, 0, width*height);
ocm1=opred2;
}
if(mode.isCD()) {
OpenCLKernel krl4 = prog.createKernel("colorTransformRGBAInPlace");
krl4.putArg(ocm1);
krl4.putArg(width*height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl4, 0, width*height);
}
// oct.getOpenCLCommandQueue().finish();
// tdb.mark("预测解码");
ocm2=tlm1.get(oct.getOpenCLContext());
if(mode.isPL()) {
OpenCLKernel krl1 = prog.createKernel("serializePlannarRGBAWithZigzag");
krl1.putArg(ocm1);
krl1.putArg(ocm2);
krl1.putArg(width*height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl1, 0,width*height);
}else {
OpenCLKernel krl2 = prog.createKernel("serializeRGBAWithZigzag");
krl2.putArg(ocm1);
krl2.putArg(ocm2);
krl2.putArg(width*height);
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl2, 0,width*height);
}
// oct.getOpenCLCommandQueue().finish();
// tdb.mark("反序列化");
OpenCLMemory dataser;
if(mode.isBP()) {
ocm3=tlm2.get(oct.getOpenCLContext());
OpenCLKernel krl0 = prog.createKernel("encodePacked");
krl0.putArg(ocm2);
krl0.putArg(ocm3);
krl0.putArg((int)pixels.byteSize());
oct.getOpenCLCommandQueue().execute1DRangeKernel(krl0, 0,pixels.byteSize());
dataser=ocm3;//
}else {
dataser=ocm2;
}
// oct.getOpenCLCommandQueue().finish();
// tdb.mark("位平面解码");
tdb.mark("编码");
serialData2=MembandBoost.allocateUninitializedByteArray(width*height*4);
tdb.mark("分配堆内存");
oct.getOpenCLCommandQueue().executeReadBuffer(dataser, dataserpseg, true,0,0,width*height*4);
tdb.mark("显存->内存");
MemorySegment.copy(dataserpseg, ValueLayout.JAVA_BYTE, 0, serialData2, 0,width*height*4);
tdb.mark("堆外内存->堆内存");
}
public KIFCompressedImageBlock encodeStage2() {
int sizeBefore=serialData2.length;
byte[]writeData=Compressors.compressZstd(serialData2);
tdb.mark("熵编码");
tdb.print();
return new KIFCompressedImageBlock(writeData,sizeBefore,width,height,input.getPosx(),input.getPosy(),mode);
}
}
+205
View File
@@ -0,0 +1,205 @@
package org.kne.codec.kif;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.kne.debug.TimeDebugger;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
/**
* KIF格式的实际解码器。
* 这个类负责读取.kif文件并解码成BufferedImage。
*/
public class KIFImageReader extends ImageReader {
private ImageInputStream inputStream;
private int width = -1;
private int height = -1;
protected KIFImageReader(ImageReaderSpi originatingProvider) {
super(originatingProvider);
}
@Override
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata);
if (input instanceof ImageInputStream) {
this.inputStream = (ImageInputStream) input;
} else {
this.inputStream = null;
}
// 重置状态
this.width = -1;
this.height = -1;
}
/**
* 读取文件头,获取图像尺寸等元数据
*/
private void readHeader() throws IOException {
if (inputStream == null) {
throw new IllegalStateException("Input not set");
}
// 文件头结构(你需要根据实际格式调整):
// Magic: 3 bytes "KIF"
// Version: 1 byte
// Width: 4 bytes (int)
// Height: 4 bytes (int)
// 更多元数据...
// 校验魔数 (已由Spi验证,但外部直接调用时仍需防御)
byte[] magic = new byte[3];
inputStream.readFully(magic);
if (magic[0] != 0x4B || magic[1] != 0x49 || magic[2] != 0x46) {
throw new IOException("无效的 KIF 文件格式: 魔数不匹配 (期望 KIF, 实际 " +
String.format("%02X %02X %02X", magic[0], magic[1], magic[2]) + ")");
}
// 读取版本
byte version = inputStream.readByte();
// 读取类型
byte type = inputStream.readByte();
// 读取宽高 (假设为大端序)
this.width = inputStream.readInt();
this.height = inputStream.readInt();
// 这里可以读取更多元数据,例如预测模式、压缩方式等
}
@Override
public int getWidth(int imageIndex) throws IOException {
if (imageIndex != 0) {
throw new IndexOutOfBoundsException("Only image index 0 is supported");
}
readHeader();
return width;
}
@Override
public int getHeight(int imageIndex) throws IOException {
if (imageIndex != 0) {
throw new IndexOutOfBoundsException("Only image index 0 is supported");
}
readHeader();
return height;
}
@Override
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
if (imageIndex != 0) {
throw new IndexOutOfBoundsException("Only image index 0 is supported");
}
readHeader();
// 返回支持的图像类型(这里只支持标准的RGB或灰度)
List<ImageTypeSpecifier> types = new ArrayList<>();
// 支持 RGB (8-bit interleaved)
types.add(ImageTypeSpecifier.createInterleaved(
java.awt.color.ColorSpace.getInstance(java.awt.color.ColorSpace.CS_sRGB),
new int[]{0, 1, 2}, // band offsets: R, G, B
0, // data type: TYPE_BYTE
false, // hasAlpha
false // isAlphaPremultiplied
));
// 支持灰度
types.add(ImageTypeSpecifier.createGrayscale(8, 0, false));
return types.iterator();
}
@Override
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
if (imageIndex != 0) {
throw new IndexOutOfBoundsException("Only image index 0 is supported");
}
TimeDebugger tdb=new TimeDebugger("读取时间",false);
readHeader();
tdb.mark("读取文件头");
AtomicReference<int[]>array=new AtomicReference<>();
int blockcount=inputStream.readInt();
ArrayList<Future<IntImageBlock>>decodeing=new ArrayList<>(blockcount);
KIFCodec.backPressure();
for(int i=0;i<blockcount;i++) {
decodeing.add( KIFCodec.decodeAndConsumeAsync(()->{
KIFCompressedImageBlock kip;
synchronized (inputStream) {
try {
kip=KIFCompressedImageBlock.readFromStream(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return kip;
},(out)->{
while(array.get()==null) {
Thread.yield();
}
out.toImage(MemorySegment.ofArray( array.get()), width, height);
}));
}
tdb.mark("读取文件并提交解码");
BufferedImage image =BufferedImageMembandBoost.createUninitializedBufferedImage(width, height);
int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
array.set(dest);
tdb.mark("分配输出图像内存");
for(Future<IntImageBlock> compressed:decodeing) {
try {
compressed.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
ExceptionTool.throwIOException(e);
}
}
tdb.mark("解码/拼接");
tdb.print();
return image;
}
@Override
public IIOMetadata getStreamMetadata() throws IOException {
return null; // 本实现不提供流元数据
}
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return null; // 本实现不提供图像元数据
}
@Override
public int getNumImages(boolean allowSearch) throws IOException {
return 1; // KIF格式只包含单张图像(文件版本)
}
@Override
public void dispose() {
super.dispose();
}
}
@@ -0,0 +1,130 @@
package org.kne.codec.kif;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
/**
* KIF格式的ImageIO服务提供者。
* 这个类负责告诉ImageIO:我能处理.kif文件,并且我能创建对应的ImageReader。
*/
public class KIFImageReaderSpi extends ImageReaderSpi {
// 格式标识信息
private static final String VENDOR_NAME = "KIFCodec Project";
private static final String VERSION = "1.0";
private static final String[] FORMAT_NAMES = {"kif", "KIF"};
private static final String[] SUFFIXES = {"kif"};
private static final String[] MIME_TYPES = {"image/kif"};
private static final String READER_CLASS_NAME = KIFImageReader.class.getName();
// 标记是否支持流式读取(不支持,因为需要随机访问)
private static final boolean SUPPORTS_STANDARD_STREAM_METADATA = false;
private static final boolean SUPPORTS_STANDARD_IMAGE_METADATA = false;
// 单例实例,避免重复注册
private static KIFImageReaderSpi instance;
public KIFImageReaderSpi() {
super(
VENDOR_NAME, // vendorName
VERSION, // version
FORMAT_NAMES, // names
SUFFIXES, // suffixes
MIME_TYPES, // MIMETypes
READER_CLASS_NAME, // readerClassName
new Class<?>[]{ImageInputStream.class}, // inputTypes
null, // writerSpiNames
SUPPORTS_STANDARD_STREAM_METADATA,
null, // nativeStreamMetadataFormatName
null, // nativeStreamMetadataFormatClassName
null, // extraStreamMetadataFormatNames
null, // extraStreamMetadataFormatClassNames
SUPPORTS_STANDARD_IMAGE_METADATA,
null, // nativeImageMetadataFormatName
null, // nativeImageMetadataFormatClassName
null, // extraImageMetadataFormatNames
null // extraImageMetadataFormatClassNames
);
}
/**
* 手动注册此插件到 ImageIO 的全局注册表。
* 调用后,ImageIO 就能识别 .kif 格式。
*
* <p>使用示例:
* <pre>
* // 在应用启动时调用一次
* KIFImageReaderSpi.register();
*
* // 之后就可以正常使用 ImageIO 了
* BufferedImage img = ImageIO.read(new File("test.kif"));
* </pre>
*
* @return true 如果注册成功,false 如果已经注册过
*/
public static void register() {
IIORegistry registry = IIORegistry.getDefaultInstance();
// 创建实例并注册
if (instance == null) {
instance = new KIFImageReaderSpi();
registry.registerServiceProvider(instance);
}
}
/**
* 从 ImageIO 的全局注册表中注销此插件。
*
*/
public static void unregister() {
IIORegistry registry = IIORegistry.getDefaultInstance();
if (instance != null) {
registry.deregisterServiceProvider(instance);
}
}
@Override
public boolean canDecodeInput(Object source) throws IOException {
if (!(source instanceof ImageInputStream)) {
return false;
}
ImageInputStream stream = (ImageInputStream) source;
// 保存当前位置
long pos = stream.getStreamPosition();
try {
// 读取文件头魔数(Magic Number
byte[] magic = new byte[3];
stream.readFully(magic);
// 检查是否为 "KIF" (0x4B 0x49 0x46)
if (magic[0] == 0x4B && magic[1] == 0x49 && magic[2] == 0x46) {
return true;
}
return false;
} finally {
// 恢复位置
stream.seek(pos);
}
}
@Override
public ImageReader createReaderInstance(Object extension) throws IOException {
return new KIFImageReader(this);
}
@Override
public String getDescription(Locale locale) {
return "KIF (KIFCodec Image Format) Image Reader";
}
}
+231
View File
@@ -0,0 +1,231 @@
package org.kne.codec.kif;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
import org.kne.debug.TimeDebugger;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import java.util.zip.Deflater;
/**
* KIF格式的实际编码器。
* 这个类负责将BufferedImage编码为.kif文件。
*/
public class KIFImageWriter extends ImageWriter {
private static final int BLOCK_SIZE=512;
private ImageOutputStream outputStream;
private static ModeStatisticsCollector modeCollector=new ModeStatisticsCollector();
public static ModeStatisticsCollector getModeCollector() {
return modeCollector;
}
protected KIFImageWriter(ImageWriterSpi originatingProvider) {
super(originatingProvider);
}
@Override
public void setOutput(Object output) {
super.setOutput(output);
if (output instanceof ImageOutputStream) {
this.outputStream = (ImageOutputStream) output;
} else {
this.outputStream = null;
}
}
@Override
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
return null;
}
@Override
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
return null;
}
@Override
public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
return null;
}
@Override
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
return null;
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
if (outputStream == null) {
throw new IllegalStateException("Output not set");
}
// 检查是否支持此图像类型
if (image.getRenderedImage() == null) {
throw new IllegalArgumentException("Image contains no RenderedImage");
}
RenderedImage renderedImage = image.getRenderedImage();
int width = renderedImage.getWidth();
int height = renderedImage.getHeight();
TimeDebugger tdb=new TimeDebugger("写入时间",false);
// 方法1:如果 RenderedImage 是 BufferedImage,可以直接操作
BufferedImage bi = null;
if (renderedImage instanceof BufferedImage) {
bi = (BufferedImage) renderedImage;
} else {
// 如果不是 BufferedImage,创建一个副本(简化处理)
bi = BufferedImageMembandBoost.createUninitializedBufferedImage(width, height) ;
Graphics2D g= (Graphics2D) bi.getGraphics();
g.setComposite(AlphaComposite.Src);
g .drawImage((Image) renderedImage, 0, 0, null);
}
/*
// 在 KIFImageWriter.write() 中
BufferedImage original = (BufferedImage) image.getRenderedImage();
// 统一转换为 TYPE_INT_ARGB
BufferedImage rgbImage =null;
if(original.getType()!=BufferedImage.TYPE_INT_ARGB) {
rgbImage = BufferedImageMembandBoost.createUninitializedBufferedImage( original.getWidth(), original.getHeight() );
Graphics2D g = rgbImage.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(original, 0, 0, null);
g.dispose();
}else {
rgbImage=original;
}*/
tdb.mark("格式转换");
List<Future<KIFCompressedImageBlock>>encoding=new ArrayList<>();
List<BlockCoord>bcd=BlockCoord.calculateBlockCoords(width,height, BLOCK_SIZE, BLOCK_SIZE);
KIFCodec.backPressure();
for(BlockCoord crd:bcd) {
final BufferedImage bi2=bi;
encoding.add( KIFCodec.encodeAsyncMultimode(()->{
return IntImageBlock.createBlock(bi2,crd);
},KIFCompressMode.getNaturalImageModes()));
}
tdb.mark("分块提交");
// ===== 写入文件头 =====
// 魔数 (3 bytes) "KIF"
outputStream.write(0x4B); // 'K'
outputStream.write(0x49); // 'I'
outputStream.write(0x46); // 'F'
// 版本号 (1 byte)
outputStream.writeByte(1);
// 图像类型 (1 byte): 0=灰度, 1=RGB, 2=RGBA
int imageType = bi.getType();
// System.out.println("类型:"+imageType);
int typeFlag = 0;
if (imageType == BufferedImage.TYPE_INT_RGB ||
imageType == BufferedImage.TYPE_3BYTE_BGR) {
typeFlag = 1;
} else if (imageType == BufferedImage.TYPE_INT_ARGB ||
imageType == BufferedImage.TYPE_4BYTE_ABGR) {
typeFlag = 2;
}
outputStream.writeByte(typeFlag);
// 宽度 (4 bytes, 大端序)
outputStream.writeInt(width);
// 高度 (4 bytes, 大端序)
outputStream.writeInt(height);
// ===== 写入压缩的图像数据 =====
outputStream.writeInt(encoding.size());
tdb.mark("写入头");
while(true) {
for (Iterator<Future<KIFCompressedImageBlock>> iterator = encoding.iterator(); iterator.hasNext();) {
Future<KIFCompressedImageBlock> v = iterator.next();
try {
if(v.isDone()) {
KIFCompressedImageBlock blk=v.get();
blk.writeToStream(outputStream);
modeCollector.addRecord(blk.getPredictMode());
iterator.remove();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
ExceptionTool.throwIOException(e);
}
}
if(encoding.isEmpty()) {
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
tdb.mark("等待编码完成");
// 注意:写入完成后记得 flush
outputStream.flush();
tdb.mark("写入文件");
tdb.print();
}
/**
* 将 BufferedImage 转换为平面像素数组(逐行,每个通道独立)
* 这是一个辅助方法,实际实现时可根据需要调整
*/
private byte[][] getPixelBands(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int bands = image.getSampleModel().getNumBands();
byte[][] bandData = new byte[bands][width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = y * width + x;
int rgb = image.getRGB(x, y);
// 根据图像类型提取各个通道
// 简化:假设为 RGB
if (bands >= 3) {
bandData[0][idx] = (byte) ((rgb >> 16) & 0xFF); // R
bandData[1][idx] = (byte) ((rgb >> 8) & 0xFF); // G
bandData[2][idx] = (byte) (rgb & 0xFF); // B
} else if (bands == 1) {
bandData[0][idx] = (byte) (rgb & 0xFF); // 灰度
}
}
}
return bandData;
}
@Override
public void dispose() {
super.dispose();
}
}
@@ -0,0 +1,94 @@
package org.kne.codec.kif;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Locale;
/**
* KIF格式的ImageIO服务提供者(Writer端)。
* 这个类负责告诉ImageIO:我能写入.kif格式,并且我能创建对应的ImageWriter。
*/
public class KIFImageWriterSpi extends ImageWriterSpi {
private static final String VENDOR_NAME = "KIFCodec Project";
private static final String VERSION = "1.0";
private static final String[] FORMAT_NAMES = {"kif", "KIF"};
private static final String[] SUFFIXES = {"kif"};
private static final String[] MIME_TYPES = {"image/kif"};
private static final String WRITER_CLASS_NAME = KIFImageWriter.class.getName();
private static final boolean SUPPORTS_STANDARD_STREAM_METADATA = false;
private static final boolean SUPPORTS_STANDARD_IMAGE_METADATA = false;
// 单例
private static KIFImageWriterSpi instance;
public KIFImageWriterSpi() {
super(
VENDOR_NAME,
VERSION,
FORMAT_NAMES,
SUFFIXES,
MIME_TYPES,
WRITER_CLASS_NAME,
new Class<?>[]{ImageOutputStream.class},
new String[]{KIFImageReaderSpi.class.getName()}, // readerSpiNames
SUPPORTS_STANDARD_STREAM_METADATA,
null, // nativeStreamMetadataFormatName
null, // nativeStreamMetadataFormatClassName
null, // extraStreamMetadataFormatNames
null, // extraStreamMetadataFormatClassNames
SUPPORTS_STANDARD_IMAGE_METADATA,
null, // nativeImageMetadataFormatName
null, // nativeImageMetadataFormatClassName
null, // extraImageMetadataFormatNames
null // extraImageMetadataFormatClassNames
);
}
// ========== 手动注册方法 ==========
public static void register() {
IIORegistry registry = IIORegistry.getDefaultInstance();
if (instance == null) {
instance = new KIFImageWriterSpi();
registry.registerServiceProvider(instance);
}
}
public static void unregister() {
IIORegistry registry = IIORegistry.getDefaultInstance();
if (instance != null) {
registry.deregisterServiceProvider(instance);
instance=null;
}
}
// ========== SPI 必须实现的方法 ==========
@Override
public boolean canEncodeImage(ImageTypeSpecifier type) {
// 检查是否支持此图像类型
// 简化版:支持大部分常见类型
int sampleSize = type.getSampleModel().getSampleSize(0);
// 支持 8-bit 灰度或 RGB
return sampleSize == 8;
}
@Override
public ImageWriter createWriterInstance(Object extension) throws IOException {
return new KIFImageWriter(this);
}
@Override
public String getDescription(Locale locale) {
return "KIF (KIFCodec Image Format) Image Writer";
}
}
@@ -0,0 +1,35 @@
package org.kne.codec.kif;
/**
* KIF 压缩模式统计收集器(线程安全)
* 专门统计 KIFCompressMode 的出现次数与概率
*/
public class ModeStatisticsCollector extends StatisticsCollector<KIFCompressMode> {
private static final ModeFormatter FORMATTER = new ModeFormatter();
/**
* 生成表格形式的统计报告
*/
@Override
public String toString() {
return formatTable(FORMATTER);
}
/**
* 生成 CSV 格式报告
*/
public String toCSV() {
return super.toCSV(FORMATTER);
}
/**
* KIFCompressMode 格式化器
*/
private static class ModeFormatter implements ItemFormatter<KIFCompressMode> {
@Override
public String format(KIFCompressMode mode) {
return mode.toString();
}
}
}
+247
View File
@@ -0,0 +1,247 @@
package org.kne.codec.kif;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 多模式并行编码的 Future 包装器
* 等待所有子任务完成选择体积最小的结果
*/
public class MultiModeFuture implements Future<KIFCompressedImageBlock> {
private final ArrayList<Future<KIFCompressedImageBlock>> futures;
private volatile KIFCompressedImageBlock bestResult;
private volatile boolean done = false;
private volatile boolean cancelled = false;
private final Object lock = new Object();
public MultiModeFuture(ArrayList< Future<KIFCompressedImageBlock>> futures) {
if (futures == null || futures.size() == 0) {
throw new IllegalArgumentException("至少需要1个子任务");
}
this.futures = futures;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (isDone()) {
return false;
}
cancelled = true;
boolean allCancelled = true;
for (Future<KIFCompressedImageBlock> f : futures) {
if (!f.cancel(mayInterruptIfRunning)) {
allCancelled = false;
}
}
return allCancelled;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean isDone() {
// 如果已经标记完成直接返回 true
if (done) {
return true;
}
// 检查是否所有子任务都已完成
boolean allDone = true;
for (Future<KIFCompressedImageBlock> f : futures) {
if (!f.isDone()) {
allDone = false;
break;
}
}
if (allDone) {
// 所有子任务完成自动合并结果不阻塞
synchronized (lock) {
if (!done) {
// 如果之前没有合并过现在合并
KIFCompressedImageBlock minBlock = null;
Exception lastException = null;
for (Future<KIFCompressedImageBlock> f : futures) {
try {
KIFCompressedImageBlock block = f.get(); // 此时不会阻塞因为 isDone() 已为 true
if (block != null) {
if (minBlock == null || block.compareTo(minBlock) < 0) {
minBlock = block;
}
}
} catch (Exception e) {
lastException = e;
}
}
if (minBlock != null||(lastException!=null)) {
bestResult = minBlock;
done = true;
}
}
}
}
return done;
}
@Override
public KIFCompressedImageBlock get() throws InterruptedException, ExecutionException {
if (done && bestResult != null) {
return bestResult;
}
synchronized (lock) {
if (done && bestResult != null) {
return bestResult;
}
// 等待所有子任务完成
Exception lastException = null;
KIFCompressedImageBlock minBlock = null;
for (Future<KIFCompressedImageBlock> f : futures) {
try {
KIFCompressedImageBlock block = f.get();
if (block != null) {
if (minBlock == null || block.compareTo(minBlock) < 0) {
minBlock = block;
}
}
} catch (ExecutionException e) {
lastException = e;
// 继续收集其他任务的结果
}
}
// 如果有至少一个成功的结果返回最小的
if (minBlock != null) {
bestResult = minBlock;
done = true;
return bestResult;
}
// 全部失败抛出最后一个异常
if (lastException != null) {
throw new ExecutionException(lastException);
}
// 理论上不会走到这里
throw new ExecutionException(new IllegalStateException("所有子任务返回了 null"));
}
}
@Override
public KIFCompressedImageBlock get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (done && bestResult != null) {
return bestResult;
}
synchronized (lock) {
if (done && bestResult != null) {
return bestResult;
}
long deadline = System.nanoTime() + unit.toNanos(timeout);
Exception lastException = null;
KIFCompressedImageBlock minBlock = null;
for (Future<KIFCompressedImageBlock> f : futures) {
long remaining = deadline - System.nanoTime();
if (remaining <= 0) {
throw new TimeoutException("等待超时");
}
try {
KIFCompressedImageBlock block = f.get(remaining, TimeUnit.NANOSECONDS);
if (block != null) {
if (minBlock == null || block.compareTo(minBlock) < 0) {
minBlock = block;
}
}
} catch (ExecutionException e) {
lastException = e;
}
}
if (minBlock != null) {
bestResult = minBlock;
done = true;
return bestResult;
}
if (lastException != null) {
throw new ExecutionException(lastException);
}
throw new ExecutionException(new IllegalStateException("所有子任务返回了 null"));
}
}
/* @Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("MultiModeFuture{\n");
// 获取各个子任务的模式如果能取到的话
// 由于 Future 可能还没完成我们只能尽量获取已完成的信息
sb.append(" 子任务数: ").append(futures.length).append("\n");
// 尝试获取每个子任务的结果如果已完成
for (int i = 0; i < futures.length; i++) {
Future<KIFCompressedImageBlock> f = futures[i];
if (f.isDone()) {
try {
// 如果已经完成可以安全获取
KIFCompressedImageBlock block = f.get();
if (block != null) {
sb.append(String.format(" 模式 %d: size=%d bytes, 压缩率=%.2f%%\n",
block.getPredictMode() & 0xFF,
block.getData().length,
(double) block.getData().length / block.getSizeBeforeCompress() * 100
));
} else {
sb.append(" 模式 ").append(i).append(": null\n");
}
} catch (Exception e) {
sb.append(" 模式 ").append(i).append(": 获取失败 (").append(e.getMessage()).append(")\n");
}
} else {
// 如果还没完成尝试获取模式需要额外传递模式信息
// 这里无法直接从 Future 中提取模式因为 Future 只存结果
// 可以在调用时传入模式编号或使用自定义 Future 子类携带模式信息
sb.append(" 子任务 ").append(i).append(": 未完成\n");
}
}
// 如果有最终结果显示
if (done && bestResult != null) {
sb.append(" 最终选择:\n");
sb.append(String.format(" 模式: %d\n", bestResult.getPredictMode() & 0xFF));
sb.append(String.format(" 体积: %d bytes\n", bestResult.getData().length));
sb.append(String.format(" 压缩率: %.2f%%\n",
(double) bestResult.getData().length / bestResult.getSizeBeforeCompress() * 100
));
sb.append(" 宽x高: ").append(bestResult.getWidth()).append("x").append(bestResult.getHeight());
} else if (done && bestResult == null) {
sb.append(" 最终结果: null (所有子任务失败)\n");
} else {
sb.append(" 状态: 未完成,仍在等待子任务...\n");
}
sb.append("}");
return sb.toString();
}*/
@Override
public String toString() {
return bestResult.toString();
}
}
+256
View File
@@ -0,0 +1,256 @@
package org.kne.codec.kif;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.kne.membandboost.MembandBoost;
/**
* int[] 像素数组与 byte[] 的相互转换工具
* 每个像素按 R, G, B 三个字节依次写入
*/
public class PixelSerializer {
// ==================== 基础序列化/反序列化 (RGB, 3通道) ====================
public static byte[] serialize(int[] pixels, int w, int h) {
int len = pixels.length;
byte[] result = MembandBoost.allocateUninitializedByteArray(len * 3);
for (int i = 0; i < len; i++) {
int pixel = pixels[i];
int idx = i * 3;
result[idx] = (byte) ((pixel >> 16) & 0xFF);
result[idx + 1] = (byte) ((pixel >> 8) & 0xFF);
result[idx + 2] = (byte) (pixel & 0xFF);
}
return result;
}
public static int[] deserialize(byte[] data, int w, int h) throws IOException {
int expectedLen = w * h * 3;
if (data.length != expectedLen) {
throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
}
int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
for (int i = 0; i < pixels.length; i++) {
int idx = i * 3;
int r = data[idx] & 0xFF;
int g = data[idx + 1] & 0xFF;
int b = data[idx + 2] & 0xFF;
pixels[i] = (r << 16) | (g << 8) | b;
}
return pixels;
}
// ==================== 合并 Zigzag (RGB, 3通道) ====================
public static byte[] serializeWithZigzag(int[] pixels, int w, int h) {
int len = pixels.length;
byte[] result = MembandBoost.allocateUninitializedByteArray(len * 3);
for (int i = 0; i < len; i++) {
int pixel = pixels[i];
byte r = (byte) ((pixel >> 16) & 0xFF);
byte g = (byte) ((pixel >> 8) & 0xFF);
byte b = (byte) (pixel & 0xFF);
byte mr = Zigzag.map(r);
byte mg = Zigzag.map(g);
byte mb = Zigzag.map(b);
int idx = i * 3;
result[idx] = mr;
result[idx + 1] = mg;
result[idx + 2] = mb;
}
return result;
}
public static int[] deserializeWithZigzag(byte[] data, int w, int h) throws IOException {
int expectedLen = w * h * 3;
if (data.length != expectedLen) {
throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
}
int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
for (int i = 0; i < pixels.length; i++) {
int idx = i * 3;
byte r = Zigzag.unmap(data[idx]);
byte g = Zigzag.unmap(data[idx + 1]);
byte b = Zigzag.unmap(data[idx + 2]);
pixels[i] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
return pixels;
}
// ==================== 平面序列化 (RGB, 3通道) ====================
public static byte[] serializePlannarWithZigzag(int[] pixels, int w, int h) {
int len = pixels.length;
byte[] result = MembandBoost.allocateUninitializedByteArray(len * 3);
int rOffset = 0;
int gOffset = len;
int bOffset = len * 2;
for (int i = 0; i < len; i++) {
int pixel = pixels[i];
byte r = (byte) ((pixel >> 16) & 0xFF);
byte g = (byte) ((pixel >> 8) & 0xFF);
byte b = (byte) (pixel & 0xFF);
result[rOffset + i] = Zigzag.map(r);
result[gOffset + i] = Zigzag.map(g);
result[bOffset + i] = Zigzag.map(b);
}
return result;
}
public static int[] deserializePlannarWithZigzag(byte[] data, int w, int h) throws IOException {
int expectedLen = w * h * 3;
if (data.length != expectedLen) {
throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
}
int len = w * h;
int[] pixels = MembandBoost.allocateUninitializedIntArray(len);
int rOffset = 0;
int gOffset = len;
int bOffset = len * 2;
for (int i = 0; i < len; i++) {
byte r = Zigzag.unmap(data[rOffset + i]);
byte g = Zigzag.unmap(data[gOffset + i]);
byte b = Zigzag.unmap(data[bOffset + i]);
pixels[i] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
return pixels;
}
// ==================== RGBA 基础序列化 (4通道) ====================
/**
* RGBA 序列化每个像素 [R, G, B, A] 四个字节交错存储
*/
public static byte[] serializeRGBA(int[] pixels, int w, int h) {
int len = pixels.length;
byte[] result = MembandBoost.allocateUninitializedByteArray(len * 4);
for (int i = 0; i < len; i++) {
int pixel = pixels[i];
int idx = i * 4;
result[idx] = (byte) ((pixel >> 16) & 0xFF);
result[idx + 1] = (byte) ((pixel >> 8) & 0xFF);
result[idx + 2] = (byte) (pixel & 0xFF);
result[idx + 3] = (byte) ((pixel >> 24) & 0xFF);
}
return result;
}
/**
* RGBA 反序列化
*/
public static int[] deserializeRGBA(byte[] data, int w, int h) throws IOException {
int expectedLen = w * h * 4;
if (data.length != expectedLen) {
throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
}
int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
for (int i = 0; i < pixels.length; i++) {
int idx = i * 4;
int r = data[idx] & 0xFF;
int g = data[idx + 1] & 0xFF;
int b = data[idx + 2] & 0xFF;
int a = data[idx + 3] & 0xFF;
pixels[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
return pixels;
}
// ==================== RGBA + Zigzag (交错) ====================
public static byte[] serializeRGBAWithZigzag(int[] pixels, int w, int h) {
int len = pixels.length;
byte[] result = MembandBoost.allocateUninitializedByteArray(len * 4);
for (int i = 0; i < len; i++) {
int pixel = pixels[i];
byte r = (byte) ((pixel >> 16) & 0xFF);
byte g = (byte) ((pixel >> 8) & 0xFF);
byte b = (byte) (pixel & 0xFF);
byte a = (byte) ((pixel >> 24) & 0xFF);
byte mr = Zigzag.map(r);
byte mg = Zigzag.map(g);
byte mb = Zigzag.map(b);
byte ma = Zigzag.map(a); // Alpha 也做 Zigzag
int idx = i * 4;
result[idx] = mr;
result[idx + 1] = mg;
result[idx + 2] = mb;
result[idx + 3] = ma;
}
return result;
}
public static int[] deserializeRGBAWithZigzag(byte[] data, int w, int h) throws IOException {
int expectedLen = w * h * 4;
if (data.length != expectedLen) {
throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
}
int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
for (int i = 0; i < pixels.length; i++) {
int idx = i * 4;
byte r = Zigzag.unmap(data[idx]);
byte g = Zigzag.unmap(data[idx + 1]);
byte b = Zigzag.unmap(data[idx + 2]);
byte a = Zigzag.unmap(data[idx + 3]);
pixels[i] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
return pixels;
}
// ==================== RGBA 平面序列化 (RRRR...GGGG...BBBB...AAAA...) ====================
public static byte[] serializePlannarRGBAWithZigzag(int[] pixels, int w, int h) {
int len = pixels.length;
byte[] result = MembandBoost.allocateUninitializedByteArray(len * 4);
int rOffset = 0;
int gOffset = len;
int bOffset = len * 2;
int aOffset = len * 3;
for (int i = 0; i < len; i++) {
int pixel = pixels[i];
byte r = (byte) ((pixel >> 16) & 0xFF);
byte g = (byte) ((pixel >> 8) & 0xFF);
byte b = (byte) (pixel & 0xFF);
byte a = (byte) ((pixel >> 24) & 0xFF);
result[rOffset + i] = Zigzag.map(r);
result[gOffset + i] = Zigzag.map(g);
result[bOffset + i] = Zigzag.map(b);
result[aOffset + i] = Zigzag.map(a); // Alpha 单独一个平面
}
return result;
}
public static int[] deserializePlannarRGBAWithZigzag(byte[] data, int w, int h) throws IOException {
int expectedLen = w * h * 4;
if (data.length != expectedLen) {
throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
}
int len = w * h;
int[] pixels = MembandBoost.allocateUninitializedIntArray(len);
int rOffset = 0;
int gOffset = len;
int bOffset = len * 2;
int aOffset = len * 3;
for (int i = 0; i < len; i++) {
byte r = Zigzag.unmap(data[rOffset + i]);
byte g = Zigzag.unmap(data[gOffset + i]);
byte b = Zigzag.unmap(data[bOffset + i]);
byte a = Zigzag.unmap(data[aOffset + i]);
pixels[i] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
return pixels;
}
// ==================== 工具方法 ====================
public static BufferedImage pixelsToBufferedImage(int[] pixels, int w, int h) {
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
int[] data = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
System.arraycopy(pixels, 0, data, 0, pixels.length);
return image;
}
}
+39
View File
@@ -0,0 +1,39 @@
package org.kne.codec.kif;
import javax.imageio.ImageIO;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import java.util.Arrays;
import java.util.Iterator;
public class PluginTest {
public static void main(String[] args) {
KIFImageReaderSpi.register();
KIFImageWriterSpi.register();
IIORegistry registry = IIORegistry.getDefaultInstance();
// 获取所有已注册的 ImageReaderSpi
Iterator<ImageReaderSpi> readerSPIs = registry.getServiceProviders(ImageReaderSpi.class, true);
System.out.println("=== 已注册的图像读取器插件 ===");
for (Iterator iterator = readerSPIs; iterator.hasNext();) {
ImageReaderSpi spi = (ImageReaderSpi) iterator.next();
System.out.println("格式: " + Arrays.toString(spi.getFormatNames()) +
" | 后缀: " + Arrays.toString(spi.getFileSuffixes()));
}
// 获取所有已注册的 ImageReaderSpi
Iterator<ImageWriterSpi> readerSPIsx = registry.getServiceProviders(ImageWriterSpi.class, true);
System.out.println("=== 已注册的图像写入器插件 ===");
for (Iterator iterator = readerSPIsx; iterator.hasNext();) {
ImageWriterSpi spi = (ImageWriterSpi) iterator.next();
System.out.println("格式: " + Arrays.toString(spi.getFormatNames()) +
" | 后缀: " + Arrays.toString(spi.getFileSuffixes()));
}
}
}
+157
View File
@@ -0,0 +1,157 @@
package org.kne.codec.kif;
import org.kne.membandboost.MembandBoost;
public class Predictor {
public static int[] predict(int[] data, int w, int h,int vx,int vy) {
int[] result=MembandBoost.allocateUninitializedIntArray(data.length);
if(vy==0) {
horizontalPredict0(data, result, w, h, vx, vy);
}else {
verticalPredict0(data, result, w, h, vx, vy);
}
return result;
}
public static int[] inverse(int[] data, int w, int h,int vx,int vy) {
int[] result=MembandBoost.allocateUninitializedIntArray( data.length);
if(vy==0) {
horizontalInverse0(data, result, w, h, vx, vy);
}else {
verticalInverse0(data, result, w, h, vx, vy);
}
return result;
}
public static void predictInPlace(int[] data, int w, int h,int vx,int vy) {
if(vy==0) {
horizontalPredict0(data, data, w, h, vx, vy);
}else {
verticalPredict0(data, data, w, h, vx, vy);
}
}
public static void inverseInPlace(int[] data, int w, int h,int vx,int vy) {
if(vy==0) {
horizontalInverse0(data, data, w, h, vx, vy);
}else {
verticalInverse0(data, data, w, h, vx, vy);
}
}
private static void horizontalPredict0(int[] datain,int[] datadout, int w, int h,int vx,int vy) {
for (int y = 0; y < h; y++) {
// 从右往左因为左边是原始值右边要用左边的原始值
for (int x = w - 1; x >= 0; x--) {
int idx = y * w + x;
int px=x+vx;
int py=y+vy;
int pred=0;
if(px>=0&&px<w&&py>=0&&py<h) {
pred=datain[py*w+px];
}
datadout[idx] = pixelSub(datain[idx] , pred);
}
}
}
private static void horizontalInverse0(int[] datain,int[] datadout, int w, int h,int vx,int vy) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int idx = y * w + x;
int px=x+vx;
int py=y+vy;
int pred=0;
if(px>=0&&px<w&&py>=0&&py<h) {
pred=datadout[py*w+px];
}
// 还原当前 = 左邻居 + 残差
datadout[idx] =pixelAdd( pred , datain[idx]);
}
}
}
private static void verticalPredict0(int[] datain,int[] datadout, int w, int h,int vx,int vy) {
for (int y = h - 1; y >= 0; y--) {
for (int x = 0; x < w; x++) {
int idx = y * w + x;
int px=x+vx;
int py=y+vy;
int pred=0;
if(px>=0&&px<w&&py>=0&&py<h) {
pred=datain[py*w+px];
}
datadout[idx] = pixelSub(datain[idx] , pred);
}
}
}
private static void verticalInverse0(int[] datain,int[] datadout, int w, int h,int vx,int vy) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int idx = y * w + x;
int px=x+vx;
int py=y+vy;
int pred=0;
if(px>=0&&px<w&&py>=0&&py<h) {
pred=datadout[py*w+px];
}
datadout[idx] =pixelAdd( pred , datain[idx]);
}
}
}
/**
* 像素安全加法将两个 RGBA 像素相加每个通道独立计算结果裁剪到 0~255
* 用于预测逆运算还原 = 预测值 + 残差
*/
public static int pixelAdd(int a, int b) {
int ar = (a >> 16) & 0xFF;
int ag = (a >> 8) & 0xFF;
int ab = a & 0xFF;
int aa = (a >> 24) & 0xFF;
int br = (b >> 16) & 0xFF;
int bg = (b >> 8) & 0xFF;
int bb = b & 0xFF;
int ba = (b >> 24) & 0xFF;
int r = ar + br;
int g = ag + bg;
int bv = ab + bb;
int av = aa + ba; // Alpha 通道也要处理
// &0xFF 替代 min/max保留低 8 自动溢出等同于裁剪
return ((av & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (bv & 0xFF);
}
/**
* 像素安全减法计算 a - b每个通道独立计算结果裁剪到 0~255
* 用于预测编码残差 = 当前像素 - 预测值
*/
public static int pixelSub(int a, int b) {
int ar = (a >> 16) & 0xFF;
int ag = (a >> 8) & 0xFF;
int ab = a & 0xFF;
int aa = (a >> 24) & 0xFF;
int br = (b >> 16) & 0xFF;
int bg = (b >> 8) & 0xFF;
int bb = b & 0xFF;
int ba = (b >> 24) & 0xFF;
int r = ar - br;
int g = ag - bg;
int bv = ab - bb;
int av = aa - ba; // Alpha 通道也要处理
// &0xFF 替代 min/max保留低 8 自动溢出等同于裁剪
return ((av & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (bv & 0xFF);
}
}
@@ -0,0 +1,219 @@
package org.kne.codec.kif;
import java.text.DecimalFormat;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
/**
* 通用统计收集器线程安全
* 统计任意对象的出现次数与概率
*
* @param <T> 被统计的对象类型
*/
public class StatisticsCollector<T> {
protected final ConcurrentHashMap<T, AtomicLong> counts = new ConcurrentHashMap<>();
protected final AtomicLong total = new AtomicLong(0);
protected final DecimalFormat df = new DecimalFormat("0.00");
/**
* 记录一次出现
*/
public void addRecord(T item) {
counts.computeIfAbsent(item, k -> new AtomicLong(0)).incrementAndGet();
total.incrementAndGet();
}
/**
* 记录多次出现
*/
public void addRecord(T item, long count) {
counts.computeIfAbsent(item, k -> new AtomicLong(0)).addAndGet(count);
total.addAndGet(count);
}
/**
* 记录另一个收集器的所有数据
*/
public void merge(StatisticsCollector<T> other) {
for (Map.Entry<T, AtomicLong> entry : other.counts.entrySet()) {
addRecord(entry.getKey(), entry.getValue().get());
}
}
/**
* 获取总次数
*/
public long getTotal() {
return total.get();
}
/**
* 获取某个对象的出现次数
*/
public long getCount(T item) {
AtomicLong count = counts.get(item);
return count == null ? 0 : count.get();
}
/**
* 获取某个对象的出现概率百分比
*/
public double getProbability(T item) {
long t = total.get();
if (t == 0) return 0.0;
return getCount(item) * 100.0 / t;
}
/**
* 获取去重对象数量
*/
public int getUniqueCount() {
return counts.size();
}
/**
* 获取所有对象按出现次数降序排列
*/
public Map<T, AtomicLong> getSorted() {
return counts.entrySet().stream()
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(old, neu) -> old,
java.util.LinkedHashMap::new
));
}
/**
* 获取所有对象按出现次数升序排列
*/
public Map<T, AtomicLong> getSortedAscending() {
return counts.entrySet().stream()
.sorted((a, b) -> Long.compare(a.getValue().get(), b.getValue().get()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(old, neu) -> old,
java.util.LinkedHashMap::new
));
}
/**
* 重置统计
*/
public void reset() {
counts.clear();
total.set(0);
}
/**
* 判断是否为空
*/
public boolean isEmpty() {
return counts.isEmpty();
}
/**
* 生成表格形式的统计报告需要子类实现格式化
*/
public String formatTable(ItemFormatter<T> formatter) {
long t = total.get();
if (t == 0) {
return "(无统计数据)";
}
StringBuilder sb = new StringBuilder();
sb.append("╔═══════════════════════════════════════════════════════════════════════════════════╗\n");
sb.append("║ 统计报告 ║\n");
sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
sb.append("║ 序号 │ 对象 │ 出现次数 │ 占比 │ 累积占比 ║\n");
sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
Map<T, AtomicLong> sorted = getSorted();
long cumulative = 0;
int index = 0;
for (Map.Entry<T, AtomicLong> entry : sorted.entrySet()) {
T item = entry.getKey();
long count = entry.getValue().get();
double pct = count * 100.0 / t;
cumulative += count;
double cumPct = cumulative * 100.0 / t;
index++;
String itemStr = formatter.format(item);
// 截断过长的字符串
if (itemStr.length() > 27) {
itemStr = itemStr.substring(0, 24) + "...";
}
sb.append(String.format("║ %4d │ %-27s │ %8d │ %6.2f%% │ %6.2f%% ║\n",
index,
itemStr,
count,
pct,
cumPct
));
}
sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
sb.append(String.format("║ 总次数: %d │ 唯一对象数: %d │ 覆盖率: %6.2f%% ║\n",
t,
getUniqueCount(),
100.0
));
sb.append("╚═══════════════════════════════════════════════════════════════════════════════════╝");
return sb.toString();
}
/**
* 生成 CSV 格式报告
*/
public String toCSV(ItemFormatter<T> formatter) {
long t = total.get();
if (t == 0) return "无数据";
StringBuilder sb = new StringBuilder();
sb.append("序号,对象,次数,占比(%),累积占比(%)\n");
Map<T, AtomicLong> sorted = getSorted();
long cumulative = 0;
int index = 0;
for (Map.Entry<T, AtomicLong> entry : sorted.entrySet()) {
T item = entry.getKey();
long count = entry.getValue().get();
double pct = count * 100.0 / t;
cumulative += count;
double cumPct = cumulative * 100.0 / t;
index++;
sb.append(String.format("%d,%s,%d,%.2f,%.2f\n",
index,
formatter.format(item),
count,
pct,
cumPct
));
}
return sb.toString();
}
@Override
public String toString() {
return formatTable((t)->{
return t.toString();
});
}
/**
* 对象格式化接口
*/
public interface ItemFormatter<T> {
String format(T item);
}
}
+59
View File
@@ -0,0 +1,59 @@
package org.kne.codec.kif;
/**
* Zigzag 映射工具
* 用于将预测残差集中在0附近的有符号数映射为熵编码友好的非负数序列
*/
public class Zigzag {
/**
* 映射单个 byte
* 00, -11, 12, -23, 24, ...
*/
public static byte map(byte x) {
int v = x; // byte 转为 int保留符号
return (byte) ((v >= 0) ? (v << 1) : ((-v << 1) - 1));
}
/**
* 逆映射单个 byte
* 00, 1-1, 21, 3-2, 42, ...
*
* 用按位与替代取余v & 1 等价于 v % 2但快 2-3
*/
public static byte unmap(byte y) {
int v = y & 0xFF; // 转为无符号 0~255
return (byte) ((v & 1) == 0 ? (v >> 1) : (-((v + 1) >> 1)));
}
/**
* 映射整个字节数组原地修改
*/
public static void mapInPlace(byte[] data) {
for (int i = 0; i < data.length; i++) {
data[i] = map(data[i]);
}
}
/**
* 逆映射整个字节数组原地修改
*/
public static void unmapInPlace(byte[] data) {
for (int i = 0; i < data.length; i++) {
data[i] = unmap(data[i]);
}
}
/**
* 映射整个 int 数组每个元素的低 8
* 注意只处理每个 int 的低 8 高位保持不变
*/
public static void mapInPlaceInt(int[] data) {
for (int i = 0; i < data.length; i++) {
int v = data[i] & 0xFF; // 取低 8 位作为有符号数
// 处理符号扩展如果最高位为 1说明是负数
byte b = (byte) v;
data[i] = (data[i] & 0xFFFFFF00) | (map(b) & 0xFF);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
package org.kne.codec.kvf;
import org.jcodec.api.FrameGrab;
import org.jcodec.api.JCodecException;
import org.jcodec.common.io.FileChannelWrapper;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Picture;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.scale.AWTUtil;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipFile;
/**
* JCodec 最小化 Demo读取 MP4 文件解码每一帧为 BufferedImage
*
* 用法java JCodecDemo /path/to/video.mp4
*/
public class H264ToKVF {
public static void main(String[] args) throws IOException, JCodecException {
// 1. 注册 KIF 插件
org.kne.codec.kif.KIFImageReaderSpi.register();
org.kne.codec.kif.KIFImageWriterSpi.register();
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
if (args.length < 1) {
System.err.println("用法: java JCodecDemo <video.mp4>");
System.exit(1);
}
File inputFile = new File(args[0]);
if (!inputFile.exists()) {
System.err.println("文件不存在: " + inputFile.getAbsolutePath());
System.exit(1);
}
File outputFile=new File("output.kvf");
System.out.println("🎬 开始转码: " + inputFile.getName());
// 1. 打开 FileChannel向上转型为 SeekableByteChannel
try ( KVFOutputStream kvo=new KVFOutputStream(new FileOutputStream(outputFile))){
try (FileInputStream fis = new FileInputStream(inputFile);
FileChannel fileChannel = fis.getChannel()) {
// 关键 SeekableByteChannel 类型接收
SeekableByteChannel channel =new FileChannelWrapper( fileChannel);
// 2. 创建帧抓取器
MP4Demuxer demux=MP4Demuxer.createMP4Demuxer(channel);
// 3. 逐帧解码
int frameCount = 0;
long startTime = System.currentTimeMillis();
Picture picture;
/* while ((picture = grab.getNativeFrame()) != null) {
frameCount++;
System.out.printf(" 已解码 %d 帧...\n", frameCount);
kvo.writeFrame(0, frameCount, picture, "kif");
}*/
long elapsed = System.currentTimeMillis() - startTime;
System.out.printf("✅ 转码完成!共 %d 帧,耗时 %dms\n", frameCount, elapsed);
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
package org.kne.codec.kvf;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import javax.imageio.ImageIO;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;
import org.kne.opencl64.OpenCLDevice;
import org.kne.opencl64.concurrent.OpenCLExecutors;
public class KVFCodec {
private static ThreadPoolExecutor cpupool ;
static {
cpupool= (ThreadPoolExecutor) Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(),
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "KVFCodec-CPU-Worker-" + threadNumber.getAndIncrement());
t.setDaemon(true); // 设置为守护线程
return t;
}
}
);
}
public static Future<byte[]> encodeIFrame(BufferedImage frame,String formattype,Consumer<byte[]>result) {
cpuBackPressure();
return cpupool.submit(()->{
ByteArrayOutputStream boos=new ByteArrayOutputStream();
ImageIO.write(frame, formattype, boos);
byte[]resultdata=boos.toByteArray();
if(result!=null) {
result.accept(resultdata);
}
return resultdata;
});
}
public static Future<byte[]> encodeIFrame(Picture picture,String formattype,Consumer<byte[]>result) {
cpuBackPressure();
return cpupool.submit(()->{
BufferedImage frame = AWTUtil.toBufferedImage(picture);
ByteArrayOutputStream boos=new ByteArrayOutputStream();
ImageIO.write(frame, formattype, boos);
byte[]resultdata=boos.toByteArray();
if(result!=null) {
result.accept(resultdata);
}
return resultdata;
});
}
public static Future<BufferedImage> decodeIFrame(byte[]data,Consumer<BufferedImage>result){
cpuBackPressure();
return cpupool.submit(()->{
ByteArrayInputStream biis=new ByteArrayInputStream(data);
BufferedImage resultframe= ImageIO.read(biis);
biis.close();
if(result!=null) {
result.accept(resultframe);
}
return resultframe;
});
}
public static void cpuBackPressure() {
while(isCPUOverloaded()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 判断是否过载调用方可据此决定是否降级处理
*/
public static boolean isCPUOverloaded() {
return cpupool.getQueue().size() > cpupool.getPoolSize() * 2;
}
}
+150
View File
@@ -0,0 +1,150 @@
package org.kne.codec.kvf;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;
import org.kne.codec.kif.ExceptionTool;
public class KVFOutputStream extends ZipOutputStream {
private ReentrantLock lock=new ReentrantLock();
private List<Future<byte[]>>tasks=Collections.synchronizedList(new ArrayList<>());
public KVFOutputStream(OutputStream out, Charset charset) {
super(out, charset);
setLevel(Deflater.NO_COMPRESSION);
}
public KVFOutputStream(OutputStream out) {
super(out);
setLevel(Deflater.NO_COMPRESSION);
}
/**
* 写入一个条目自动处理锁
*/
public void putEntry(String name, byte[] data) throws IOException {
lock.lock();
try {
putNextEntry(new ZipEntry(name));
write(data);
closeEntry();
} finally {
lock.unlock();
}
}
/**
* 写入 meta.json自动使用 UTF-8
*/
public void writeMeta(String json) throws IOException {
putEntry("meta.json", json.getBytes(StandardCharsets.UTF_8));
}
/**
* 写入一个帧KIF 数据
*/
public void writeFrame(int channelIndex, int frameIndex, byte[] kifData,String format) throws IOException {
putEntry(String.format("video/%d/frame_%04d.%s",channelIndex, frameIndex,format), kifData);
}
//private List
public void writeFrame(int channelIndex, int frameIndex,BufferedImage frame,String format) throws IOException {
removeDone();
Future<byte[]>encodeFuture= KVFCodec.encodeIFrame(frame, format, (result)->{
try {
writeFrame(channelIndex, frameIndex, result,format);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
tasks.add(encodeFuture);
}
public void writeFrame(int channelIndex, int frameIndex, Picture picture, String format) throws IOException {
removeDone();
Future<byte[]>encodeFuture= KVFCodec.encodeIFrame(picture, format, (result)->{
try {
writeFrame(channelIndex, frameIndex, result,format);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
tasks.add(encodeFuture);
}
private void waitForDone()throws IOException {
while(true) {
removeDone();
if(tasks.isEmpty()) {
return;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void removeDone()throws IOException {
AtomicReference<Throwable>e=new AtomicReference<>();
tasks.removeIf((v)->{
boolean b=v.isDone();
if(b!=false) {
try{
v.get();
}catch(ExecutionException | InterruptedException thr) {
if(e.get()==null) {
e.set(thr);
}
}
}
return b;
});
Throwable th=e.get();
ExceptionTool.throwIOException(th);
}
@Override
public void finish() throws IOException {
waitForDone();
lock.lock();
try {
super.finish();
} finally {
lock.unlock();
}
}
@Override
public void close() throws IOException {
waitForDone();
lock.lock();
try {
super.close();
} finally {
lock.unlock();
}
}
}
+269
View File
@@ -0,0 +1,269 @@
package org.kne.codec.kvf;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.MP4TrackType;
import org.jcodec.containers.mp4.boxes.*;
import org.jcodec.containers.mp4.demuxer.AbstractMP4DemuxerTrack;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mp4.demuxer.MP4DemuxerTrack;
import java.io.File;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* MP4 元数据分析器
* 获取音视频轨道帧率GOP编码格式等完整元数据
*/
public class MP4Analyzer {
public static void main(String[] args) throws Exception {
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
if (args.length < 1) {
System.err.println("用法: java MP4Analyzer <video.mp4>");
System.exit(1);
}
File file = new File(args[0]);
if (!file.exists()) {
System.err.println("文件不存在: " + file.getAbsolutePath());
System.exit(1);
}
System.out.println("========================================");
System.out.println("📹 MP4 元数据分析: " + file.getName());
System.out.println("========================================\n");
try (SeekableByteChannel channel = NIOUtils.readableChannel(file);
MP4Demuxer demuxer = MP4Demuxer.createMP4Demuxer(channel)) {
// 1. 视频轨道信息
analyzeVideoTrack(demuxer);
// 2. 音频轨道信息
analyzeAudioTracks(demuxer);
// 3. 其他轨道
analyzeOtherTracks(demuxer);
// 4. 容器元数据
analyzeContainerInfo(demuxer);
// 5. GOP 结构分析
analyzeGOP(demuxer);
// 6. 统计信息
printSummary(demuxer);
}
}
/**
* 分析视频轨道
*/
private static void analyzeVideoTrack(MP4Demuxer demuxer) throws Exception {
System.out.println("🎬 视频轨道 (Video Tracks)");
System.out.println("───────────────────────────────────────────────");
List<DemuxerTrack> videoTracks = demuxer.getVideoTracks();
if (videoTracks.isEmpty()) {
System.out.println(" ❌ 无视频轨道");
System.out.println();
return;
}
for (int i = 0; i < videoTracks.size(); i++) {
DemuxerTrack track = videoTracks.get(i);
DemuxerTrackMeta meta = track.getMeta();
System.out.printf(" 轨道 %d:\n", i);
System.out.printf(" 编码格式: %s\n", meta.getCodec());
System.out.printf(" 分辨率: %dx%d\n", meta.getVideoCodecMeta().getSize().getWidth(),
meta.getVideoCodecMeta().getSize().getHeight());
System.out.printf(" 帧率: %.3f fps\n", meta.getTotalFrames() / meta.getTotalDuration());
System.out.printf(" 总帧数: %d\n", meta.getTotalFrames());
System.out.printf(" 时长: %.2f 秒\n", meta.getTotalDuration());
System.out.printf(" 像素格式: %s\n", meta.getVideoCodecMeta().getColor());
// System.out.printf(" 参考帧数: %d\n", meta.getVideoCodecMeta().getRefFrames());
// System.out.printf(" 是否可变帧率: %s\n", meta.isVariableFps() ? "" : "");
// 尝试获取更多信息
if (track instanceof MP4DemuxerTrack) {
MP4DemuxerTrack m4Track = (MP4DemuxerTrack) track;
// System.out.printf(" 轨道 ID: %d\n", m4Track.getTrackId());
System.out.printf(" 轨道类型: %s\n", m4Track.getType());
}
System.out.println();
}
}
/**
* 分析音频轨道
*/
private static void analyzeAudioTracks(MP4Demuxer demuxer) {
System.out.println("🎵 音频轨道 (Audio Tracks)");
System.out.println("───────────────────────────────────────────────");
List<DemuxerTrack> audioTracks = demuxer.getAudioTracks();
if (audioTracks.isEmpty()) {
System.out.println(" ❌ 无音频轨道");
System.out.println();
return;
}
for (int i = 0; i < audioTracks.size(); i++) {
DemuxerTrack track = audioTracks.get(i);
DemuxerTrackMeta meta = track.getMeta();
System.out.printf(" 轨道 %d:\n", i);
System.out.printf(" 编码格式: %s\n", meta.getCodec());
System.out.printf(" 采样率: %d Hz\n", meta.getAudioCodecMeta().getSampleRate());
System.out.printf(" 声道数: %d\n", meta.getAudioCodecMeta().getChannelCount());
System.out.printf(" 采样位数: %d bit\n", meta.getAudioCodecMeta().getSampleSize());
System.out.printf(" 总帧数: %d\n", meta.getTotalFrames());
System.out.printf(" 时长: %.2f 秒\n", meta.getTotalDuration());
System.out.println();
}
}
/**
* 分析其他轨道字幕等
*/
private static void analyzeOtherTracks(MP4Demuxer demuxer) {
System.out.println("📝 其他轨道 (Other Tracks)");
System.out.println("───────────────────────────────────────────────");
List<AbstractMP4DemuxerTrack> otherTracks = demuxer.getTracks();
if (otherTracks.isEmpty()) {
System.out.println(" ❌ 无其他轨道");
System.out.println();
return;
}
for (DemuxerTrack track : otherTracks) {
DemuxerTrackMeta meta = track.getMeta();
System.out.printf(" 轨道类型: %s, 编码: %s\n",
meta.getCodec(), meta.getCodec());
}
System.out.println();
}
/**
* 分析容器元数据
*/
private static void analyzeContainerInfo(MP4Demuxer demuxer) {
System.out.println("📦 容器信息 (Container Info)");
System.out.println("───────────────────────────────────────────────");
// 获取 FileTypeBox (ftyp)
/* FileTypeBox ftyp = demuxer.getBoxes(FileTypeBox.class).get(0);
System.out.printf(" 主要品牌: %s\n", ftyp.getMajorBrand());
System.out.printf(" 兼容品牌: %s\n", String.join(", ", ftyp.getCompatibleBrands()));
System.out.printf(" 版本: %d\n", ftyp.getVersion());
*/
// 获取 MovieBox (moov)
MovieBox moov = demuxer.getMovie();
System.out.printf(" 时长 (timescale): %d\n", moov.getTimescale());
System.out.println();
}
/**
* 分析 GOP 结构
*/
private static void analyzeGOP(MP4Demuxer demuxer) throws Exception {
System.out.println("🎯 GOP 结构分析 (GOP Structure)");
System.out.println("───────────────────────────────────────────────");
List<DemuxerTrack> videoTracks = demuxer.getVideoTracks();
if (videoTracks.isEmpty()) {
System.out.println(" ❌ 无视频轨道");
System.out.println();
return;
}
DemuxerTrack videoTrack = videoTracks.get(0);
DemuxerTrackMeta meta = videoTrack.getMeta();
// 获取 I 帧位置
int[] seekFrames = meta.getSeekFrames();
if (seekFrames == null || seekFrames.length == 0) {
System.out.println(" ⚠️ 无法获取 I 帧位置");
System.out.println();
return;
}
System.out.printf(" I 帧数量: %d\n", seekFrames.length);
System.out.printf(" 平均 GOP 大小: %.1f 帧\n",
(double) meta.getTotalFrames() / seekFrames.length);
// 显示前 10 GOP
System.out.println(" 前 10 个 I 帧位置:");
for (int i = 0; i < Math.min(10, seekFrames.length); i++) {
int nextIFrame = (i + 1 < seekFrames.length) ? seekFrames[i + 1] : meta.getTotalFrames();
int gopSize = nextIFrame - seekFrames[i];
System.out.printf(" GOP %d: I 帧位置 %d, GOP 大小 %d 帧\n",
i, seekFrames[i], gopSize);
}
// 检查是否有 B
boolean hasBFrame = false;
try {
// 重置轨道位置
// 读取前 100 帧判断
// 这里简化判断
} catch (Exception e) {
// 忽略
}
System.out.println();
}
/**
* 打印统计摘要
*/
private static void printSummary(MP4Demuxer demuxer) {
System.out.println("📊 统计摘要");
System.out.println("───────────────────────────────────────────────");
List<DemuxerTrack> videoTracks = demuxer.getVideoTracks();
List<DemuxerTrack> audioTracks = demuxer.getAudioTracks();
System.out.printf(" 视频轨道: %d\n", videoTracks.size());
System.out.printf(" 音频轨道: %d\n", audioTracks.size());
if (!videoTracks.isEmpty()) {
DemuxerTrackMeta meta = videoTracks.get(0).getMeta();
System.out.printf(" 视频编码: %s\n", meta.getCodec());
Size size = meta.getVideoCodecMeta().getSize();
System.out.printf(" 分辨率: %dx%d\n", size.getWidth(), size.getHeight());
System.out.printf(" 帧率: %.3f fps\n", meta.getTotalFrames() / meta.getTotalDuration());
System.out.printf(" 总帧数: %d\n", meta.getTotalFrames());
System.out.printf(" 时长: %.2f 秒 (%.2f 分钟)\n",
meta.getTotalDuration(), meta.getTotalDuration() / 60.0);
int[] seekFrames = meta.getSeekFrames();
if (seekFrames != null) {
System.out.printf(" GOP 数量: %d\n", seekFrames.length);
System.out.printf(" 平均 GOP 大小: %.1f 帧\n",
(double) meta.getTotalFrames() / seekFrames.length);
}
}
if (!audioTracks.isEmpty()) {
DemuxerTrackMeta meta = audioTracks.get(0).getMeta();
System.out.printf(" 音频编码: %s\n", meta.getCodec());
System.out.printf(" 采样率: %d Hz\n", meta.getAudioCodecMeta().getSampleRate());
System.out.printf(" 声道数: %d\n", meta.getAudioCodecMeta().getChannelCount());
}
System.out.println();
System.out.println("✅ 分析完成!");
}
}
+129
View File
@@ -0,0 +1,129 @@
package org.kne.debug;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
final class Int implements Comparable<Int>,Cloneable {
public int value;
public Int(int value) {
super();
this.value = value;
}
@Override
public int hashCode() {
return value;
}
@Override
public Object clone() throws CloneNotSupportedException {
// TODO Զɵķ
return super.clone();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Int other = (Int) obj;
if (value != other.value)
return false;
return true;
}
@Override
public String toString() {
return new Integer(value).toString();
}
@Override
public int compareTo(Int o) {
return o.value-value;
}
}
public class Debuger extends Thread {
Map <List<StackTraceElement>,Object[]>md=new HashMap<List<StackTraceElement>,Object[]>();
@Override
public void run() {
while(true){
for(int iz=0;iz<100;iz++){
Map<Thread, StackTraceElement[]> m=Thread.getAllStackTraces();
Set<Entry<Thread, StackTraceElement[]>> s=m.entrySet();
Iterator<Entry<Thread, StackTraceElement[]>> i=s.iterator();
while(i.hasNext()){
Entry<Thread, StackTraceElement[]>elements =i.next();
StackTraceElement[]value=elements.getValue();
List<StackTraceElement> l=new ArrayList<StackTraceElement>(value.length);
for (int j = 0; j < value.length; j++) {
l.add(value[j]);
}
if(!l.isEmpty())
if(md.containsKey(l)){
((Int)md.get(l)[0]).value++;
}else{
md.put(l,new Object[]{new Int(1),elements.getKey()} );
}
}
}
ArrayList<Entry<List<StackTraceElement>, Object[]>> tmp=new ArrayList();
Set<Entry<List<StackTraceElement>, Object[]>> sn=md.entrySet();
Iterator<Entry<List<StackTraceElement>, Object[]>> in=sn.iterator();
while(in.hasNext()){
Entry<List<StackTraceElement>, Object[]>elements =in.next();
tmp.add(elements);
}
tmp.sort(new Comparator<Entry<List<StackTraceElement>, Object[]>>() {
@Override
public int compare(Entry<List<StackTraceElement>, Object[]> o1,
Entry<List<StackTraceElement>, Object[]> o2) {
return ((Int)o2.getValue()[0]).value-((Int)o1.getValue()[0]).value;
}
});
for(int d=0;d<tmp.size();d++){
Entry<List<StackTraceElement>, Object[]>elements=tmp.get(d);
if(((Thread)(elements.getValue()[1])).getName().startsWith("Decode")){
}else{
continue;
}
List<StackTraceElement> k=elements.getKey();
StringBuilder sb=new StringBuilder();
for(int d2=0;d2<k.size();d2++){
sb.append(' ');sb.append(k.get(d2));
}
System.err.println(elements.getValue()[0]+":"+elements.getValue()[1]+"="+sb);
}
System.err.println("--------------------------------------------------------------------------------------------------------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Զɵ catch
e.printStackTrace();
}
}
}
}
+180
View File
@@ -0,0 +1,180 @@
package org.kne.debug;
import java.util.ArrayList;
import java.util.List;
/**
* 高性能插桩计时器
* 用于在代码中插入时间测量点输出各阶段耗时
*
* 用法:
* private static final boolean DEBUG = false; // 生产环境关掉
* TimeDebugger td = new TimeDebugger("编码流程", DEBUG);
* td.mark("读取图片");
* // ... 读取图片代码 ...
* td.mark("预测");
* // ... 预测代码 ...
* td.mark("压缩");
* // ... 压缩代码 ...
* td.print();
*
* DEBUG = false 所有方法调用被 JIT 优化为空操作零开销
*/
public final class TimeDebugger {
private final boolean enabled;
private final String name;
private final List<TimingPoint> points;
private long lastNanoTime;
private long startNanoTime;
/**
* 创建一个计时器
* @param name 本次测量的名称用于输出标题
* @param enabled true=启用计时false=所有操作跳过零开销
*/
public TimeDebugger(String name, boolean enabled) {
this.enabled = enabled;
this.name = name;
// enabled=false 时不分配 ArrayList 内存
this.points = enabled ? new ArrayList<>() : null;
if (enabled) {
this.lastNanoTime = System.nanoTime();
this.startNanoTime = this.lastNanoTime;
} else {
this.lastNanoTime = 0;
this.startNanoTime = 0;
}
}
/**
* 标记一个时间点
* @param label 当前阶段的名称
*/
public void mark(String label) {
if (!enabled) return;
long now = System.nanoTime();
long durationNs = now - lastNanoTime;
lastNanoTime = now;
points.add(new TimingPoint(label, durationNs));
}
/**
* 标记一个时间点并打印到控制台实时输出方便跟踪长流程
* @param label 当前阶段的名称
*/
public void markAndPrint(String label) {
if (!enabled) return;
mark(label);
System.out.printf(" ⏱️ %s: %.3fms\n", label, getLastDurationMs());
}
/**
* 获取上一个阶段的耗时毫秒
*/
public double getLastDurationMs() {
if (!enabled || points.isEmpty()) return 0;
return points.get(points.size() - 1).durationMs();
}
/**
* 获取总耗时毫秒
*/
public double getTotalMs() {
if (!enabled) return 0;
return (lastNanoTime - startNanoTime) / 1_000_000.0;
}
/**
* 打印所有计时结果
*/
public void print() {
if (!enabled) return;
StringBuilder sb = new StringBuilder();
sb.append("═══════════════════════════════════════════════\n");
sb.append(" ⏱️ ").append(name).append("\n");
sb.append("═══════════════════════════════════════════════\n");
double total = 0;
for (int i = 0; i < points.size(); i++) {
TimingPoint p = points.get(i);
double ms = p.durationMs();
total += ms;
sb.append(String.format(" %-20s: %8.3fms (累计: %8.3fms)\n",
p.label, ms, total));
}
sb.append("───────────────────────────────────────────────\n");
sb.append(String.format(" %-20s: %8.3fms\n", "总计", total));
sb.append("═══════════════════════════════════════════════");
System.err.println(sb.toString());
}
/**
* 重置计时器不清除已有记录用于分段测量
*/
public void reset() {
if (!enabled) return;
this.lastNanoTime = System.nanoTime();
this.startNanoTime = this.lastNanoTime;
if (points != null) {
this.points.clear();
}
}
/**
* 检查是否启用
*/
public boolean isEnabled() {
return enabled;
}
/**
* 内部计时点
*/
private static final class TimingPoint {
final String label;
final long durationNs;
TimingPoint(String label, long durationNs) {
this.label = label;
this.durationNs = durationNs;
}
double durationMs() {
return durationNs / 1_000_000.0;
}
}
// ==================== 静态方法全局默认实例 ====================
private static TimeDebugger defaultDebugger;
/**
* 获取全局默认计时器简化用法不需要 new
* 默认启用如需关闭请使用 get(boolean)
*/
public static TimeDebugger get() {
return get(true);
}
/**
* 获取全局默认计时器可指定开关
* @param enabled true=启用false=关闭
*/
public static TimeDebugger get(boolean enabled) {
if (defaultDebugger == null || defaultDebugger.isEnabled() != enabled) {
defaultDebugger = new TimeDebugger("默认流程", enabled);
}
return defaultDebugger;
}
/**
* 重置全局默认计时器
*/
public static void resetDefault() {
if (defaultDebugger != null) {
defaultDebugger.reset();
}
}
}