This commit is contained in:
2026-07-26 22:35:06 +08:00
commit c92cf914fc
58 changed files with 5954 additions and 0 deletions
+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
* 0→0, -1→1, 1→2, -2→3, 2→4, ...
*/
public static byte map(byte x) {
int v = x; // byte 转为 int(保留符号)
return (byte) ((v >= 0) ? (v << 1) : ((-v << 1) - 1));
}
/**
* 逆映射单个 byte
* 0→0, 1→-1, 2→1, 3→-2, 4→2, ...
*
* 用按位与替代取余: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();
}
}
}