本帖最后由 嫂子 于 2024-10-4 01:52 编辑
阅读前准备
8Bit=1Byte
4Byte=1Int32
Bit(位)的用处 比较典型的还有大数据去重。比如13位电话号码去重
优势就是 大量减少数据体积(压缩)
帖子来由:
看到 记——我是如何把2.2MB的黑白位图压成93KB的?_精易论坛 (125.la)
虽写如下代码。如下代码适合学习原理 注释完整,语言不是重点。关键是原理
逻辑比较乱所以 洗写了一个Delphi的代码。带完整注释和原理
代码如下:
[Delphi] 纯文本查看 复制代码 const //定义俩常量
ImageWidth = 32; //图片宽度
ImageHeight = 32; //图片高度
{压缩位图到文件}
procedure CompressBitmap(const Bitmap: TBitmap; const FileName: string);
var
BitSet: TBits; //位集合
Stream: TFileStream; //文件流
i, j, ByteIndex: Integer; //索引
BitValue: Byte; //字节
begin
BitSet := TBits.Create(); // 创建位集合
BitSet.Size := ImageWidth * ImageHeight; //位集合大小=长x宽
try
for i := 0 to ImageHeight - 1 do
begin
for j := 0 to ImageWidth - 1 do
begin
// 若黑色则置1(就是True),白色为 0 (就是False)
if Bitmap.Canvas.Pixels[j, i] = clBlack then
BitSet.Bits[i * ImageWidth + j] := True;
end;
end;
// 保存到文件
Stream := TFileStream.Create(FileName, fmCreate);
try
//8个位->组合一个Byte字节
for ByteIndex := 0 to (BitSet.Size div 8) - 1 do
begin
BitValue := 0;
//8个位一次。遍历8个位将对应的位值设置到Byte里。
for j := 0 to 7 do
begin
if BitSet.Bits[ByteIndex * 8 + j] then
BitValue := BitValue or (1 shl j);
end;
//然后把这个Byte写出到文件里
Stream.Write(BitValue, SizeOf(Byte));
end;
finally
Stream.Free;
end;
finally
BitSet.Free;
end;
end;
{从文件读取还原回bmp}
procedure DecompressBitmap(const FileName: string; var Bitmap: TBitmap);
var
Stream: TFileStream;
BitSet: TBits;
i, j, ByteIndex: Integer;
BitValue: Byte;
begin
BitSet := TBits.Create();
BitSet.Size := ImageWidth * ImageHeight;
try
// 从文件读取位数据
Stream := TFileStream.Create(FileName, fmOpenRead);
try
for ByteIndex := 0 to (BitSet.Size div 8) - 1 do
begin
Stream.Read(BitValue, SizeOf(Byte));
for j := 0 to 7 do
begin
if (ByteIndex * 8 + j) < BitSet.Size then
BitSet.Bits[ByteIndex * 8 + j] := ((BitValue and (1 shl j)) <> 0);
end;
end;
finally
Stream.Free;
end;
// 创建 BMP 图像
Bitmap.Width := ImageWidth;
Bitmap.Height := ImageHeight;
Bitmap.PixelFormat := pf32bit; // 使用 32 位像素格式
for i := 0 to ImageHeight - 1 do
begin
for j := 0 to ImageWidth - 1 do
begin
if BitSet.Bits[i * ImageWidth + j] then
Bitmap.Canvas.Pixels[j, i] := clBlack
else
Bitmap.Canvas.Pixels[j, i] := clWhite;
end;
end;
finally
BitSet.Free;
end;
end;
唯一比较难懂的地方
压缩的时候 BitSet.Bits[i * ImageWidth + j] := True; 这个索引为什么这么计算
解答:
这个32*32的 Bit集合本质是看作一个 32x32的点阵或者二维数组。
那么我们代码中一般是用 二维[x,y]来表示一个元素。但是数组本质存在内存中如何根据x,y 定位呢?
答案就是所谓的线性索引
在内存中,数组通常是以线性方式存储的。为了将二维数组映射到一维数组(或位数组),我们需要计算一个线性索引。
线性索引的计算公式为:Index = Row * Width + Column,其中 Row 是行索引,Width 是每行的元素数量(在这个例子中是 ImageWidth),Column 是列索引。
比如
第一行
x=0 y=0 ->索引=x*32(数据的宽度)+y=0 对应内存中的0位置(也就是第一个位置)
x=0 y=1 ->索引=x*32(数据的宽度)+y=1 对应内存中的1位置(也就是第二个位置)
x=0 y=2 ->索引=x*32(数据的宽度)+y=2 对应内存中的2位置(也就是第三个位置)
以此类推到
x=0 y=31 ->索引=x*32(数据的宽度)+y=31 对应内存中的2位置(也就是第32个位置)
第一行结束
第二行
x=1 y=0 ->索引=x*32(数据的宽度)+y=32 对应内存中的0位置(也就是第33个位置)
x=1 y=1 ->索引=x*32(数据的宽度)+y=33 对应内存中的1位置(也就是第34个位置)
x=1 y=2 ->索引=x*32(数据的宽度)+y=34 对应内存中的2位置(也就是第35个位置)
以此类推到
x=1 y=31 ->索引=x*32(数据的宽度)+y=63 对应内存中的2位置(也就是第64个位置)
第二行结束
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 2 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 3 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 4 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 5 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 6 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 7 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 8 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 9 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 10 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 11 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 12 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 13 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 14 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 15 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 16 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 17 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 18 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 19 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 20 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 21 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 22 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 23 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 24 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 25 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 26 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 27 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 28 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 29 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 30 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 31 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
用这样的表格理解更直观
实际效果:
调用:
[Delphi] 纯文本查看 复制代码 procedure TForm12.Button2Click(Sender: TObject);
begin
{压缩}
var bmp := Tbitmap.Create;
bmp.LoadFromFile('32.bmp');
CompressBitmap(bmp, 'Yasuo.dat');
bmp.Free;
{解压还原}
var newbmp := Tbitmap.Create;
DecompressBitmap('Yasuo.dat', newbmp);
//显示到图片框看一下对不对
image1.Picture.Bitmap.Assign(newbmp);
newbmp.Free;
end;
32位系统 1个32位整数 占用4字节(Byte) = 4*8=32个位(Bit)
32*32 个Bit 恰好就是32*4字节=128字节
运行结果也符合预期 属性看大小 就是128字节
|