开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 902|回复: 19
收起左侧

[其它源码] [Delphi]黑白BMP压缩(BitSet压缩法)[建议学习]

[复制链接]
发表于 2024-10-4 01:14:27 | 显示全部楼层 |阅读模式   浙江省金华市
分享源码
界面截图: -
是否带模块: -
备注说明: -
本帖最后由 嫂子 于 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;


原图.bmp 压缩.png

32位系统 1个32位整数 占用4字节(Byte) = 4*8=32个位(Bit)
32*32 个Bit 恰好就是32*4字节=128字节

运行结果也符合预期 属性看大小 就是128字节






评分

参与人数 5好评 +1 精币 +6 收起 理由
wa690602724 + 1 感谢分享,很给力!~
光影魔术 + 1 + 1 开源精神必须支持~
kyo9766 + 1 感谢分享,很给力!~
1355301564 + 2 YYDS~!
多多帅吧 + 1 YYDS~!

查看全部评分


签到天数: 22 天

 楼主| 发表于 2024-10-4 01:39:18 | 显示全部楼层   浙江省金华市
知识补充

Byte 如何获取到每一个Bit的值(也就是二进制)

解答:

ByteValue and (1 shl i)

1左移i   然后和 Byte值  位于  得到的值 不等于0 则表示1 否则表示0

得到的就是二进制(每一个Bit的值)

比如
Byte=165  二进制是 10100101
  
子程序名返回值类型公开备 注
_按钮1_被单击  
变量名类 型静态数组备 注
Byte字节型 
i整数型 
Byte = 165
计次循环首 (8, i)
判断 (位与 (Byte, 左移 (1, i - 1)) ≠ 0)
编辑框1.加入文本 (“1”)
编辑框1.加入文本 (“0”)

计次循环尾 ()

回复 支持 反对

使用道具 举报

结帖率:79% (15/19)

签到天数: 16 天

发表于 2024-10-19 09:51:20 | 显示全部楼层   河北省廊坊市
厉害啊。。。。。。。。。。
回复 支持 反对

使用道具 举报

结帖率:100% (1/1)

签到天数: 13 天

发表于 2024-10-9 14:07:51 | 显示全部楼层   广西壮族自治区柳州市
感谢分享
回复 支持 反对

使用道具 举报

签到天数: 22 天

发表于 2024-10-7 09:38:15 | 显示全部楼层   浙江省宁波市
感谢分享,支持开源!!!
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)

签到天数: 20 天

发表于 2024-10-6 17:36:47 | 显示全部楼层   湖北省十堰市
值得学习,谢谢呀!..........
回复 支持 反对

使用道具 举报

结帖率:50% (7/14)

签到天数: 10 天

发表于 2024-10-6 10:18:35 | 显示全部楼层   安徽省安庆市
感谢分享。谢谢啦
回复 支持 反对

使用道具 举报

签到天数: 6 天

发表于 2024-10-6 08:31:11 | 显示全部楼层   广东省汕尾市
不错,学习了。。。
回复 支持 反对

使用道具 举报

结帖率:100% (7/7)

签到天数: 19 天

发表于 2024-10-5 15:57:39 | 显示全部楼层   湖北省咸宁市
感谢分享,很给力!~
回复 支持 反对

使用道具 举报

结帖率:100% (3/3)

签到天数: 14 天

发表于 2024-10-5 14:21:17 | 显示全部楼层   山东省青岛市
来学习一下原理,感谢分享
回复 支持 反对

使用道具 举报

结帖率:89% (16/18)

签到天数: 11 天

发表于 2024-10-5 10:14:02 | 显示全部楼层   广东省广州市
感谢分享,很给力!~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表