0xAA55 发表于 2014-6-20 20:42:57

【图像】抖动算法实现真彩色图片高细节256色降级处理【旧帖,效果不好,勿用】

这里所说的高细节并不是我们所说的HDR,也不是用浮点数进行纹理存储……而是在尽可能保证图片不变得“难看”的情况下把图片降级为256色(尤其是对于存储GIF图片非常具有利用价值)。
对于256色的图片都有一个调色板 Palette(或颜色表 Color Table)。这个颜色表是如何得到的呢?有很多种方法:
1、用随机数,取得256种颜色的RGB,组成调色板。
2、使用固定的调色板
3、用八叉树算法计算调色板。

其中方法1要看人品。。。人品好的话颜色刚好适合这个图像,人品不好的话就呵呵了。
方法2的调色板意在把24位真彩色的所有颜色中,均匀取出256种颜色。虽然色域很广,但是每种颜色的细节都不高。
方法3能取得接近完美的调色板。但是这个算法比较费内存。特别是图像特别大的时候。

方法3的实现方法,请看我的这个帖子:【C】八叉树算法:BMP颜色降级生成调色板的算法

接下来言归正传:抖动算法的实现原理。
假设我只有黑色和白色两种颜色,然后我要实现如下图的效果,应该怎么让画面更真实?


很简单,我们只需要把黑白的像素点排列一下。我想大家用过碳素笔都知道碳素笔这东西只能画出纯色的线,而不能像铅笔一样画出深浅不同的线条。但是仍然有一种艺术叫“线描”,就是用碳素笔画交叉线实现灰度的效果。那么我们也使用类似的方法,通过排列黑白两色的像素实现灰度的效果。因此处理后的图像效果如下:


至于这个黑白两色的像素怎么排列呢?这里就要讲到最基础的抖动算法:黑白两色的抖动算法。
我们可以看到这些像素的排列很有规律。那么这个规律到底是什么规律呢?我们其实是把一张“亮度矩阵”平铺到图像上,然后通过判断图像中的像素的值是否大于亮度矩阵图的像素的值来判断是白色还是黑色。亮度矩阵是什么样的呢?请看下图,是亮度矩阵的放大图。


然后我们把亮度矩阵图平铺到原图上,根据比较原图和亮度矩阵图的对应像素的结果,得到最终的颜色。


有关彩色图像的抖动算法,我自己提出的这个是根据空间四边形向量逼近指定颜色,用抖动矩阵来选取颜色的算法。亲测效果不好用,请大家不要用。
我来讲讲我的算法。这个是我自己想出来的算法,我并没有参考任何的相关资料。
我的想法是这样的:
假设给定任意颜色(R,G,B),让我从一个调色板里面取得一个合适的颜色来替代这个颜色,从而实现位图从24位真彩色降级为8位索引颜色。那么第一步就是从调色板里面找出最接近的颜色。
我们把颜色信息(R,G,B)当做一个三维空间的坐标,它的取值范围从(0,0,0)到(255,255,255),然后我们就可以利用三维空间中计算两点距离的公式找出最相近的颜色了。
我们拿Win7自带的企鹅壁纸做测试。首先看原图:

然后我们通过取得它的最相近颜色,先初步把它降级为256色。

以此为基础,接下来我要讲我的算法原理了。

首先,我们要从256个调色板颜色中,找出4个能包住原始颜色的颜色。所谓的“包住原始颜色”指的是把原始颜色(R,G,B)看做一个三维空间的坐标。然后我们也把调色板中所有的颜色的RGB值看成坐标。那么这样的话,256个调色板项,相当于空间中的256个点。我们再从这些颜色中找到4个点组成一个三棱锥,让原始颜色在三棱锥内部。之后我们就可以通过把抖动算法当成线性插值从而在4个颜色中选出最合适的颜色。

那么这4个颜色肯定首选距离原始颜色最接近的颜色。我的算法是,先找出距离原始颜色最近的颜色,然后取得“对面的”最接近的颜色。这个“对面的”我不好解释,不过我可以用另外一种方法解释,就是取得的这个点,和刚才的两个点,能组成一个钝角三角形,然后原始颜色的位置就在这个三角形的钝角上。
这一步下来,我们取得的颜色值就不像上面图那么像了。

然后找出第三个点。第三个点的要求是必须能和前两个点组合的三角形上能找到原始颜色的点到这个三角形的投影,同时也是所有的这些个点中距离原始颜色最近的点。

最后就是第四个点。这个点是最后一步取得的点。这个点和前面三个点组成一个四面体,这个四面体能包住这个原始颜色的点。
当这个四面体建立后,我们最后要做的就是插值。第一个点、第二个点和原始颜色的点组成一个平面,然后第三个点、第四个点和原始颜色的点组成一个平面,分别计算第一个点、第二个点到第二个平面的距离,然后分别计算第三个点、第四个点到第一个平面的距离,这样就能做好颜色1、2的插值和颜色3、4的插值。做好以后再做最终的插值。计算距离即可。
而这里所说的插值,其实是通过亮度矩阵判断点的颜色值,进行抖动。

看起来很不错,不过这个算法有个缺点,第三、第四步很容易因为找到的点颜色差别太离谱而出现严重的失真情况。就像下图这样:
原图是下面这幅图(随便找的)

处理后,可以看到第三步、第四步取得了过于离谱的颜色:

那么我的解决办法,是通过筛选,把距离过远的颜色筛除。经过这个优化以后就是如下的效果了:

这样第三步、第四步取得的奇怪颜色就被筛掉了。效果好多了!

为了便于大家理解,我决定把VB写的这个源码公开给大家看。VERSION 5.00
Begin VB.Form frmMain
   Caption         =   "取得调色板"
   ClientHeight    =   6465
   ClientLeft      =   120
   ClientTop       =   450
   ClientWidth   =   16440
   LinkTopic       =   "frmMain"
   OLEDropMode   =   1'Manual
   ScaleHeight   =   431
   ScaleMode       =   3'Pixel
   ScaleWidth      =   1096
   StartUpPosition =   3'窗口缺省
   Begin VB.PictureBox picDither
      Align         =   3'Align Left
      BorderStyle   =   0'None
      Height          =   6465
      Left            =   0
      ScaleHeight   =   431
      ScaleMode       =   3'Pixel
      ScaleWidth      =   457
      TabIndex      =   6
      Top             =   0
      Visible         =   0   'False
      Width         =   6855
      Begin VB.HScrollBar HSDither
         Height          =   255
         Left            =   1440
         Max             =   0
         TabIndex      =   12
         TabStop         =   0   'False
         Top             =   3000
         Width         =   2415
      End
      Begin VB.PictureBox picColor4
         AutoRedraw      =   -1'True
         BackColor       =   &H00000000&
         BorderStyle   =   0'None
         Height          =   495
         Left            =   1800
         ScaleHeight   =   33
         ScaleMode       =   3'Pixel
         ScaleWidth      =   33
         TabIndex      =   11
         Top             =   0
         Visible         =   0   'False
         Width         =   495
      End
      Begin VB.PictureBox picColor3
         AutoRedraw      =   -1'True
         BackColor       =   &H00000000&
         BorderStyle   =   0'None
         Height          =   495
         Left            =   1200
         ScaleHeight   =   33
         ScaleMode       =   3'Pixel
         ScaleWidth      =   33
         TabIndex      =   10
         Top             =   0
         Visible         =   0   'False
         Width         =   495
      End
      Begin VB.PictureBox picColor2
         AutoRedraw      =   -1'True
         BackColor       =   &H00000000&
         BorderStyle   =   0'None
         Height          =   495
         Left            =   600
         ScaleHeight   =   33
         ScaleMode       =   3'Pixel
         ScaleWidth      =   33
         TabIndex      =   9
         Top             =   0
         Visible         =   0   'False
         Width         =   495
      End
      Begin VB.PictureBox picColor1
         AutoRedraw      =   -1'True
         BackColor       =   &H00000000&
         BorderStyle   =   0'None
         Height          =   495
         Left            =   0
         ScaleHeight   =   33
         ScaleMode       =   3'Pixel
         ScaleWidth      =   33
         TabIndex      =   8
         Top             =   0
         Visible         =   0   'False
         Width         =   495
      End
      Begin VB.PictureBox picResult
         AutoRedraw      =   -1'True
         BackColor       =   &H00000000&
         BorderStyle   =   0'None
         Height          =   495
         Left            =   2400
         ScaleHeight   =   33
         ScaleMode       =   3'Pixel
         ScaleWidth      =   33
         TabIndex      =   7
         Top             =   0
         Visible         =   0   'False
         Width         =   495
      End
   End
   Begin VB.PictureBox picRightPanel
      Align         =   4'Align Right
      BorderStyle   =   0'None
      Height          =   6465
      Left            =   15345
      ScaleHeight   =   431
      ScaleMode       =   3'Pixel
      ScaleWidth      =   73
      TabIndex      =   0
      Top             =   0
      Width         =   1095
      Begin VB.PictureBox picProgress
         BackColor       =   &H8000000C&
         Height          =   255
         Left            =   0
         ScaleHeight   =   13
         ScaleMode       =   3'Pixel
         ScaleWidth      =   69
         TabIndex      =   14
         Top             =   4320
         Width         =   1095
         Begin VB.CommandButton cmdProgress
            Enabled         =   0   'False
            Height          =   195
            Left            =   0
            Style         =   1'Graphical
            TabIndex      =   15
            Top             =   0
            Width         =   1035
         End
      End
      Begin VB.CheckBox ChRandomPalette
         Caption         =   "产生随机调色板"
         Height          =   615
         Left            =   0
         Style         =   1'Graphical
         TabIndex      =   13
         Top             =   3600
         Width         =   1095
      End
      Begin VB.PictureBox picPal
         AutoRedraw      =   -1'True
         BackColor       =   &H00000000&
         BorderStyle   =   0'None
         Height          =   1095
         Left            =   0
         ScaleHeight   =   73
         ScaleMode       =   3'Pixel
         ScaleWidth      =   73
         TabIndex      =   5
         Top             =   2400
         Width         =   1095
      End
      Begin VB.OptionButton OpDitherPic
         Caption         =   "抖动图"
         Enabled         =   0   'False
         Height          =   495
         Left            =   0
         Style         =   1'Graphical
         TabIndex      =   4
         Top             =   1920
         Value         =   -1'True
         Width         =   1095
      End
      Begin VB.OptionButton OpSrcPic
         Caption         =   "原图"
         Enabled         =   0   'False
         Height          =   495
         Left            =   0
         Style         =   1'Graphical
         TabIndex      =   3
         Top             =   1440
         Width         =   1095
      End
      Begin VB.CommandButton cmdDither
         Caption         =   "抖动"
         Enabled         =   0   'False
         Height          =   615
         Left            =   0
         TabIndex      =   1
         Top             =   0
         Width         =   1095
      End
   End
   Begin VB.PictureBox picSrcPic
      AutoRedraw      =   -1'True
      AutoSize      =   -1'True
      BorderStyle   =   0'None
      Height          =   255
      Left            =   0
      ScaleHeight   =   255
      ScaleWidth      =   135
      TabIndex      =   2
      Top             =   0
      Visible         =   0   'False
      Width         =   135
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'==============================================================================
'作者:0xAA55
'论坛:http://www.0xaa55.com/
'版权所有 (C) 2013-2014 技术宅的结界
'请保留原作者信息,否则视为侵权。
'------------------------------------------------------------------------------

Option Explicit

Private Const COLORS_MAX As Long = 256
Private Const COLORS_BITS As Long = 8
Private Const DIST_MAX As Long = 200000

Private Type RGBQUAD
    B As Byte
    G As Byte
    R As Byte
    X As Byte
End Type

Private Type BITMAPINFO24
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

Private Type BITMAPINFOPAL
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
    Palette(COLORS_MAX - 1) As RGBQUAD
End Type

Private Declare Function CreateOctreePaletteFromHBITMAP Lib "..\Octree.dll" (ByVal hDC&, ByVal hBitmap&, ByVal Width&, ByVal Height&, ByVal MaxColors&, ByVal ColorBits&, P As RGBQUAD) As Long
Private Declare Function GetBitmapPitch Lib "..\Octree.dll" (ByVal BitCount As Integer, ByVal Width As Long) As Long
Private Declare Function GetDIBits Lib "gdi32.dll" (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO24, ByVal wUsage As Long) As Long
Private Declare Function SetDIBits Lib "gdi32.dll" (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFOPAL, ByVal wUsage As Long) As Long
Private Const DIB_RGB_COLORS = 0 'color table in RGBs
Private Const DIB_PAL_COLORS = 1 'color table in palette indices

Dim BMIF24 As BITMAPINFO24
Dim BMIFPAL As BITMAPINFOPAL
Dim PW&, PH&
Dim LM() As Byte

Dim QuitLoop As Boolean

Dim SrcPixels() As Byte

Dim Values1() As Byte
Dim Values2() As Byte
Dim Values3() As Byte
Dim Values4() As Byte
Dim Vec1R() As Long, Vec1G() As Long, Vec1B() As Long
Dim Vec2R() As Long, Vec2G() As Long, Vec2B() As Long
Dim Vec3R() As Long, Vec3G() As Long, Vec3B() As Long
Dim Vec4R() As Long, Vec4G() As Long, Vec4B() As Long

Private Const MaxDist As Double = 64
Private Const MaxDistSq As Double = MaxDist * MaxDist

Private Const RefreshInterval As Single = 0.1
Dim ProgWidth As Single

Sub DrawProgress(ByVal Prog As Double)
cmdProgress.Left = (Prog - 1) * ProgWidth
picProgress.Refresh
End Sub

Private Sub cmdDither_Click()
Dim DestPixels() As Byte
Dim Pitch1 As Long, Pitch2 As Long
Dim X&, Y&
Dim I&, LStart1&
Dim L&, LStart2&
Dim K&, DistanceSq As Long, NewDistSq As Long
Dim RDiff As Long, GDiff As Long, BDiff As Long

Dim Tm!, NTm!

cmdDither.Enabled = False
picDither.Visible = True
picDither.Visible = True
Form_Resize

Pitch1 = GetBitmapPitch(24, PW)
Pitch2 = GetBitmapPitch(8, PW)

Erase Values1, Values2, Values3, Values4, SrcPixels
Erase Vec1R, Vec1G, Vec1B
Erase Vec2R, Vec2G, Vec2B
Erase Vec3R, Vec3G, Vec3B
Erase Vec4R, Vec4G, Vec4B
ReDim SrcPixels(Pitch1 * PH - 1)
ReDim DestPixels(Pitch2 * PH - 1)
ReDim Values1(UBound(DestPixels))
ReDim Values2(UBound(DestPixels))
ReDim Values3(UBound(DestPixels))
ReDim Values4(UBound(DestPixels))
ReDim Vec1R(UBound(DestPixels)), Vec1G(UBound(DestPixels)), Vec1B(UBound(DestPixels))
ReDim Vec2R(UBound(DestPixels)), Vec2G(UBound(DestPixels)), Vec2B(UBound(DestPixels))
ReDim Vec3R(UBound(DestPixels)), Vec3G(UBound(DestPixels)), Vec3B(UBound(DestPixels))
ReDim Vec4R(UBound(DestPixels)), Vec4G(UBound(DestPixels)), Vec4B(UBound(DestPixels))

GetDIBits picSrcPic.hDC, picSrcPic.Image.Handle, 0, PH, SrcPixels(0), BMIF24, DIB_RGB_COLORS

'==============================================================================
'步骤1:取得最相近的颜色
'------------------------------------------------------------------------------
LStart1 = 0
LStart2 = 0
picColor1.Visible = True
For Y = 0 To PH - 1
    I = LStart1
    L = LStart2
    For X = 0 To PW - 1
      DistanceSq = 255& * 255 * 3
      For K = 0 To 255
            RDiff = CLng(BMIFPAL.Palette(K).R) - SrcPixels(I + 2)
            GDiff = CLng(BMIFPAL.Palette(K).G) - SrcPixels(I + 1)
            BDiff = CLng(BMIFPAL.Palette(K).B) - SrcPixels(I + 0)
            NewDistSq = RDiff * RDiff + GDiff * GDiff + BDiff * BDiff
            If NewDistSq < DistanceSq And NewDistSq <= MaxDistSq Then
                Vec1R(L) = RDiff
                Vec1G(L) = GDiff
                Vec1B(L) = BDiff
                Values1(L) = K
                DestPixels(L) = K
                DistanceSq = NewDistSq
            End If
      Next
      I = I + 3
      L = L + 1
    Next
    NTm = Timer
    If NTm - Tm >= RefreshInterval Then
      Tm = NTm
      SetDIBits picColor1.hDC, picColor1.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
      picColor1.PaintPicture picSrcPic.Image, 0, 0, PW, PH - Y, 0, 0, PW, PH - Y
      DrawProgress Y / (PH - 1)
      picColor1.Refresh
      DoEvents
      If QuitLoop Then Exit For
    End If
    LStart1 = LStart1 + Pitch1
    LStart2 = LStart2 + Pitch2
Next
SetDIBits picColor1.hDC, picColor1.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
DrawProgress 1
picColor1.Refresh

'==============================================================================
'步骤2:取得和上一步相反的颜色
'------------------------------------------------------------------------------
LStart1 = 0
LStart2 = 0
picColor2.Visible = True
For Y = 0 To PH - 1
    I = LStart1
    L = LStart2
    For X = 0 To PW - 1
      DistanceSq = 255& * 255 * 3
      Values2(L) = Values1(L)
      For K = 0 To 255
            RDiff = CLng(BMIFPAL.Palette(K).R) - SrcPixels(I + 2)
            GDiff = CLng(BMIFPAL.Palette(K).G) - SrcPixels(I + 1)
            BDiff = CLng(BMIFPAL.Palette(K).B) - SrcPixels(I + 0)
            If RDiff * Vec1R(L) + GDiff * Vec1G(L) + BDiff * Vec1B(L) < 0 Then
                NewDistSq = RDiff * RDiff + GDiff * GDiff + BDiff * BDiff
                If NewDistSq < DistanceSq And NewDistSq <= MaxDistSq Then
                  Vec2R(L) = RDiff
                  Vec2G(L) = GDiff
                  Vec2B(L) = BDiff
                  Values2(L) = K
                  DestPixels(L) = K
                  DistanceSq = NewDistSq
                End If
            End If
      Next
      I = I + 3
      L = L + 1
    Next
    NTm = Timer
    If NTm - Tm >= RefreshInterval Then
      Tm = NTm
      SetDIBits picColor2.hDC, picColor2.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
      DrawProgress Y / (PH - 1)
      picColor2.Refresh
      DoEvents
      If QuitLoop Then Exit For
    End If
    LStart1 = LStart1 + Pitch1
    LStart2 = LStart2 + Pitch2
Next
SetDIBits picColor2.hDC, picColor2.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
DrawProgress 1
picColor2.Refresh

'==============================================================================
'步骤3:取得和上两步相反的颜色
'------------------------------------------------------------------------------
LStart1 = 0
LStart2 = 0
picColor3.Visible = True
For Y = 0 To PH - 1
    I = LStart1
    L = LStart2
    For X = 0 To PW - 1
      DistanceSq = 255& * 255 * 3
      Values3(L) = Values2(L)
      For K = 0 To 255
            RDiff = CLng(BMIFPAL.Palette(K).R) - SrcPixels(I + 2)
            GDiff = CLng(BMIFPAL.Palette(K).G) - SrcPixels(I + 1)
            BDiff = CLng(BMIFPAL.Palette(K).B) - SrcPixels(I + 0)
            If RDiff * Vec1R(L) + GDiff * Vec1G(L) + BDiff * Vec1B(L) < 0 And _
               RDiff * Vec2R(L) + GDiff * Vec2G(L) + BDiff * Vec2B(L) < 0 Then
                NewDistSq = RDiff * RDiff + GDiff * GDiff + BDiff * BDiff
                If NewDistSq < DistanceSq And NewDistSq <= MaxDistSq Then
                  Vec3R(L) = RDiff
                  Vec3G(L) = GDiff
                  Vec3B(L) = BDiff
                  Values3(L) = K
                  DestPixels(L) = K
                  DistanceSq = NewDistSq
                End If
            End If
      Next
      I = I + 3
      L = L + 1
    Next
    NTm = Timer
    If NTm - Tm >= RefreshInterval Then
      Tm = NTm
      SetDIBits picColor3.hDC, picColor3.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
      DrawProgress Y / (PH - 1)
      picColor3.Refresh
      DoEvents
      If QuitLoop Then Exit For
    End If
    LStart1 = LStart1 + Pitch1
    LStart2 = LStart2 + Pitch2
Next
SetDIBits picColor3.hDC, picColor3.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
DrawProgress 1
picColor3.Refresh

'==============================================================================
'步骤4:取得和上三步相反的颜色
'------------------------------------------------------------------------------
LStart1 = 0
LStart2 = 0
picColor4.Visible = True
For Y = 0 To PH - 1
    I = LStart1
    L = LStart2
    For X = 0 To PW - 1
      DistanceSq = 255& * 255 * 3
      Values4(L) = Values3(L)
      For K = 0 To 255
            RDiff = CLng(BMIFPAL.Palette(K).R) - SrcPixels(I + 2)
            GDiff = CLng(BMIFPAL.Palette(K).G) - SrcPixels(I + 1)
            BDiff = CLng(BMIFPAL.Palette(K).B) - SrcPixels(I + 0)
            If RDiff * Vec1R(L) + GDiff * Vec1G(L) + BDiff * Vec1B(L) < 0 And _
               RDiff * Vec2R(L) + GDiff * Vec2G(L) + BDiff * Vec2B(L) < 0 And _
               RDiff * Vec3R(L) + GDiff * Vec3G(L) + BDiff * Vec3B(L) < 0 Then
                NewDistSq = RDiff * RDiff + GDiff * GDiff + BDiff * BDiff
                If NewDistSq < DistanceSq And NewDistSq <= MaxDistSq Then
                  Vec4R(L) = RDiff
                  Vec4G(L) = GDiff
                  Vec4B(L) = BDiff
                  Values4(L) = K
                  DestPixels(L) = K
                  DistanceSq = NewDistSq
                End If
            End If
      Next
      I = I + 3
      L = L + 1
    Next
    NTm = Timer
    If NTm - Tm >= RefreshInterval Then
      Tm = NTm
      SetDIBits picColor4.hDC, picColor4.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
      DrawProgress Y / (PH - 1)
      picColor4.Refresh
      DoEvents
      If QuitLoop Then Exit For
    End If
    LStart1 = LStart1 + Pitch1
    LStart2 = LStart2 + Pitch2
Next
SetDIBits picColor4.hDC, picColor4.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
DrawProgress 1
picColor4.Refresh

'==============================================================================
'步骤5:将上面四步取得的颜色进行抖动混合
'------------------------------------------------------------------------------
LStart1 = 0
LStart2 = 0
picResult.Visible = True
Dim DitherValue As Long
For Y = 0 To PH - 1
    I = LStart1
    L = LStart2
    For X = 0 To PW - 1
      DitherValue = LM((X And &HF) + (Y And &HF) * &H10)
      If Values1(L) = Values2(L) And Values2(L) = Values3(L) And Values3(L) = Values4(L) Then '只有一个颜色
            DestPixels(L) = Values1(L)
      ElseIf Values1(L) <> Values2(L) And Values2(L) = Values3(L) And Values3(L) = Values4(L) Then '抖动颜色1、2
            
            '          Src
            '         /|~"-,_
            '          / |   ~"-,_
            '         /|          ~"-,_
            '      /   |               ~"-,_
            '       /    |                  ~"-,_
            'Values1~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Values2
            
            Dim Vec1To2R As Long, Vec1To2G As Long, Vec1To2B As Long
            Dim Vec1To2Dist As Double, ProjLen As Double
            Vec1To2R = Vec2R(L) - Vec1R(L)
            Vec1To2G = Vec2G(L) - Vec1G(L)
            Vec1To2B = Vec2B(L) - Vec1B(L)
            Vec1To2Dist = Sqr(CDbl(Vec1To2R) * Vec1To2R + CDbl(Vec1To2G) * Vec1To2G + CDbl(Vec1To2B) * Vec1To2B)
            ProjLen = (-Vec1R(L) * Vec1To2R - Vec1G(L) * Vec1To2G - Vec1B(L) * Vec1To2B) / Vec1To2Dist
            If ProjLen * 255 / Vec1To2Dist <= DitherValue Then DestPixels(L) = Values1(L) Else DestPixels(L) = Values2(L)
      ElseIf Values1(L) <> Values2(L) And Values2(L) <> Values3(L) And Values3(L) = Values4(L) Then '抖动颜色1、2、3
            
            'P2,
            '|\ ~"-,_
            '| \   ~"-,_
            '|P,__      ~"-,_
            '| /    ~~""--,,__ ~"-,_
            '|L               ~~""--===,_
            'P1~~~~~~~~~~~~~~~~~~~~~~~~~~~P3
            
            Dim Plane123A As Double, Plane123B As Double, Plane123C As Double, Plane123D As Double
            Dim Plane123ABCLen As Double
            Dim Vec2To3R As Long, Vec2To3G As Long, Vec2To3B As Long
            Vec1To2R = Vec2R(L) - Vec1R(L)
            Vec1To2G = Vec2G(L) - Vec1G(L)
            Vec1To2B = Vec2B(L) - Vec1B(L)
            Vec2To3R = Vec3R(L) - Vec2R(L)
            Vec2To3G = Vec3G(L) - Vec2G(L)
            Vec2To3B = Vec3B(L) - Vec2B(L)
            
            Plane123A = Vec1To2G * Vec2To3B - Vec1To2B * Vec2To3G
            Plane123B = Vec1To2B * Vec2To3R - Vec1To2R * Vec2To3B
            Plane123C = Vec1To2R * Vec2To3G - Vec1To2G * Vec2To3R
            Plane123D = -(Plane123A * BMIFPAL.Palette(Values1(L)).R + Plane123B * BMIFPAL.Palette(Values1(L)).G + Plane123C * BMIFPAL.Palette(Values1(L)).B)
            
            Plane123ABCLen = Sqr(Plane123A * Plane123A + Plane123B * Plane123B + Plane123C * Plane123C)
            Plane123A = Plane123A / Plane123ABCLen
            Plane123B = Plane123B / Plane123ABCLen
            Plane123C = Plane123C / Plane123ABCLen
            Plane123D = Plane123D / Plane123ABCLen
            
            Dim PlaneFace23A As Double, PlaneFace23B As Double, PlaneFace23C As Double, PlaneFace23D As Double
            Dim PlaneFace23ABCLen As Double
            PlaneFace23A = Vec1G(L) * Plane123C - Vec1B(L) * Plane123B
            PlaneFace23B = Vec1B(L) * Plane123A - Vec1R(L) * Plane123C
            PlaneFace23C = Vec1R(L) * Plane123B - Vec1G(L) * Plane123A
            PlaneFace23D = -(PlaneFace23A * SrcPixels(I + 2) + PlaneFace23B * SrcPixels(I + 1) + PlaneFace23C * SrcPixels(I + 0))
            
            PlaneFace23ABCLen = Sqr(PlaneFace23A * PlaneFace23A + PlaneFace23B * PlaneFace23B + PlaneFace23C * PlaneFace23C)
            PlaneFace23A = PlaneFace23A / PlaneFace23ABCLen
            PlaneFace23B = PlaneFace23B / PlaneFace23ABCLen
            PlaneFace23C = PlaneFace23C / PlaneFace23ABCLen
            PlaneFace23D = PlaneFace23D / PlaneFace23ABCLen
            
            Dim P2ToPlaneDist As Double, P3ToPlaneDist As Double, PlaneCutPosition As Double
            P2ToPlaneDist = Abs(BMIFPAL.Palette(Values2(L)).R * PlaneFace23A + BMIFPAL.Palette(Values2(L)).G * PlaneFace23B + BMIFPAL.Palette(Values2(L)).B * PlaneFace23C + PlaneFace23D)
            P3ToPlaneDist = Abs(BMIFPAL.Palette(Values3(L)).R * PlaneFace23A + BMIFPAL.Palette(Values3(L)).G * PlaneFace23B + BMIFPAL.Palette(Values3(L)).B * PlaneFace23C + PlaneFace23D)
            PlaneCutPosition = P2ToPlaneDist / (P2ToPlaneDist + P3ToPlaneDist)
            
            If PlaneCutPosition * 255 <= DitherValue Then DestPixels(L) = Values2(L) Else DestPixels(L) = Values3(L)
            
            Dim PointOnPlaneAnd2To3VecR As Double, PointOnPlaneAnd2To3VecG As Double, PointOnPlaneAnd2To3VecB As Double
            PointOnPlaneAnd2To3VecR = CDbl(BMIFPAL.Palette(Values2(L)).R) + Vec2To3R * PlaneCutPosition - SrcPixels(I + 2)
            PointOnPlaneAnd2To3VecG = CDbl(BMIFPAL.Palette(Values2(L)).G) + Vec2To3G * PlaneCutPosition - SrcPixels(I + 1)
            PointOnPlaneAnd2To3VecB = CDbl(BMIFPAL.Palette(Values2(L)).B) + Vec2To3B * PlaneCutPosition - SrcPixels(I + 0)
            
            Dim VecToThatPointR As Double, VecToThatPointG As Double, VecToThatPointB As Double, VecToThatPointLen As Double
            VecToThatPointR = PointOnPlaneAnd2To3VecR - Vec1R(L)
            VecToThatPointG = PointOnPlaneAnd2To3VecG - Vec1G(L)
            VecToThatPointB = PointOnPlaneAnd2To3VecB - Vec1B(L)
            VecToThatPointLen = Sqr(VecToThatPointR * VecToThatPointR + VecToThatPointG * VecToThatPointG + VecToThatPointB * VecToThatPointB)
            VecToThatPointR = VecToThatPointR / VecToThatPointLen
            VecToThatPointG = VecToThatPointG / VecToThatPointLen
            VecToThatPointB = VecToThatPointB / VecToThatPointLen
            
            ProjLen = (-Vec1R(L) * VecToThatPointR - Vec1G(L) * VecToThatPointG - Vec1B(L) * VecToThatPointB) / VecToThatPointLen
            
            If ProjLen * 255 <= DitherValue Then DestPixels(L) = Values1(L)
            
      Else '抖动颜色1、2、3、4
            Dim Plane12PA As Double, Plane12PB As Double, Plane12PC As Double, Plane12PD As Double, Plane12PABCLen As Double
            Dim Plane34PA As Double, Plane34PB As Double, Plane34PC As Double, Plane34PD As Double, Plane34PABCLen As Double
            
            Plane12PA = Vec1G(L) * Vec2B(L) - Vec1B(L) * Vec2G(L)
            Plane12PB = Vec1B(L) * Vec2R(L) - Vec1R(L) * Vec2B(L)
            Plane12PC = Vec1R(L) * Vec2G(L) - Vec1G(L) * Vec2R(L)
            
            Plane34PA = Vec3G(L) * Vec4B(L) - Vec3B(L) * Vec4G(L)
            Plane34PB = Vec3B(L) * Vec4R(L) - Vec3R(L) * Vec4B(L)
            Plane34PC = Vec3R(L) * Vec4G(L) - Vec3G(L) * Vec4R(L)
            
            Plane12PD = -(Plane12PA * SrcPixels(I + 2) + Plane12PB * SrcPixels(I + 1) + Plane12PC * SrcPixels(I + 0))
            Plane34PD = -(Plane34PA * SrcPixels(I + 2) + Plane34PB * SrcPixels(I + 1) + Plane34PC * SrcPixels(I + 0))
            
            Plane12PABCLen = Sqr(Plane12PA * Plane12PA + Plane12PB * Plane12PB + Plane12PC * Plane12PC)
            Plane34PABCLen = Sqr(Plane34PA * Plane34PA + Plane34PB * Plane34PB + Plane34PC * Plane34PC)
            
            Plane12PA = Plane12PA / Plane12PABCLen
            Plane12PB = Plane12PB / Plane12PABCLen
            Plane12PC = Plane12PC / Plane12PABCLen
            Plane12PD = Plane12PD / Plane12PABCLen
            
            Plane34PA = Plane34PA / Plane34PABCLen
            Plane34PB = Plane34PB / Plane34PABCLen
            Plane34PC = Plane34PC / Plane34PABCLen
            Plane34PD = Plane34PD / Plane34PABCLen
            
            Dim Distance1ToP34 As Double, Distance2ToP34 As Double
            Dim Distance3ToP12 As Double, Distance4ToP12 As Double
            
            Distance1ToP34 = Abs(Vec1R(L) * Plane34PA + Vec1G(L) * Plane34PB + Vec1B(L) * Plane34PC + Plane34PD)
            Distance2ToP34 = Abs(Vec2R(L) * Plane34PA + Vec2G(L) * Plane34PB + Vec2B(L) * Plane34PC + Plane34PD)
            Distance3ToP12 = Abs(Vec3R(L) * Plane12PA + Vec3G(L) * Plane12PB + Vec3B(L) * Plane12PC + Plane12PD)
            Distance4ToP12 = Abs(Vec4R(L) * Plane12PA + Vec4G(L) * Plane12PB + Vec4B(L) * Plane12PC + Plane12PD)
            
            Dim P12Cut34 As Double, P34Cut12 As Double
            P12Cut34 = Distance3ToP12 + (Distance3ToP12 + Distance4ToP12)
            P34Cut12 = Distance1ToP34 + (Distance1ToP34 + Distance2ToP34)
            
            Dim Value12 As Byte, Value34 As Byte
            If P12Cut34 * 255 <= DitherValue Then Value34 = Values3(L) Else Value34 = Values4(L)
            If P34Cut12 * 255 <= DitherValue Then Value12 = Values1(L) Else Value12 = Values2(L)
            
            Vec1To2R = Vec2R(L) - Vec1R(L)
            Vec1To2G = Vec2G(L) - Vec1G(L)
            Vec1To2B = Vec2B(L) - Vec1B(L)
            Dim Vec3To4R As Long, Vec3To4G As Long, Vec3To4B As Long
            Vec3To4R = Vec4R(L) - Vec3R(L)
            Vec3To4G = Vec4G(L) - Vec3G(L)
            Vec3To4B = Vec4B(L) - Vec3B(L)
            
            Dim CutPoint1R As Double, CutPoint1G As Double, CutPoint1B As Double
            Dim CutPoint2R As Double, CutPoint2G As Double, CutPoint2B As Double
            
            CutPoint1R = Vec1R(L) + Vec1To2R * P34Cut12
            CutPoint1G = Vec1G(L) + Vec1To2G * P34Cut12
            CutPoint1B = Vec1B(L) + Vec1To2B * P34Cut12
            
            CutPoint2R = Vec3R(L) + Vec3To4R * P12Cut34
            CutPoint2G = Vec3G(L) + Vec3To4G * P12Cut34
            CutPoint2B = Vec3B(L) + Vec3To4B * P12Cut34
            
            Dim Dist12 As Double, Dist34 As Double
            Dist12 = Sqr(CutPoint1R * CutPoint1R + CutPoint1G * CutPoint1G + CutPoint1B * CutPoint1B)
            Dist34 = Sqr(CutPoint2R * CutPoint2R + CutPoint2G * CutPoint2G + CutPoint2B * CutPoint2B)
      
            If Dist12 * 255 / (Dist12 + Dist34) <= DitherValue Then DestPixels(L) = Value12 Else DestPixels(L) = Value34
            
      End If
      I = I + 3
      L = L + 1
    Next
    NTm = Timer
    If NTm - Tm >= RefreshInterval Then
      Tm = NTm
      SetDIBits picResult.hDC, picResult.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
      DrawProgress Y / (PH - 1)
      picResult.Refresh
      DoEvents
      If QuitLoop Then Exit For
    End If
    LStart1 = LStart1 + Pitch1
    LStart2 = LStart2 + Pitch2
Next
SetDIBits picResult.hDC, picResult.Image.Handle, 0, PH, DestPixels(0), BMIFPAL, DIB_RGB_COLORS
DrawProgress 1
picResult.Refresh


cmdDither.Enabled = False

OpSrcPic.Enabled = True
OpDitherPic.Enabled = True
OpDitherPic.Value = True
End Sub

Private Sub Form_Load()
With BMIF24
    .biSize = 40
    .biPlanes = 1
    .biBitCount = 24
End With
With BMIFPAL
    .biSize = 40
    .biPlanes = 1
    .biBitCount = COLORS_BITS
    .biClrUsed = COLORS_MAX
    .biClrImportant = COLORS_MAX
End With
LM = LoadResData(101, "LIGHTMATRIX")
Randomize Timer
ProgWidth = picProgress.ScaleWidth
End Sub

Function Lerp(ByVal V1 As Long, ByVal V2 As Long, ByVal Val_0_255 As Long) As Long
Lerp = V1 + (V2 - V1) * Val_0_255 \ 255
End Function

Private Sub Form_OLEDragDrop(Data As DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single)
On Local Error GoTo eHandlr
Picture = LoadPicture(Data.Files(1))
PW = ScaleX(Picture.Width, vbHimetric, vbPixels)
PH = ScaleY(Picture.Height, vbHimetric, vbPixels)
picSrcPic.Move 0, 0, PW, PH
picSrcPic.PaintPicture Picture, 0, 0
picColor1.Cls
picColor2.Cls
picColor3.Cls
picColor4.Cls
picResult.Cls
picColor1.Visible = False
picColor2.Visible = False
picColor3.Visible = False
picColor4.Visible = False
picResult.Visible = False
picColor1.Move PW * 0, 0, PW, PH
picColor2.Move PW * 1, 0, PW, PH
picColor3.Move PW * 2, 0, PW, PH
picColor4.Move PW * 3, 0, PW, PH
picResult.Move PW * 4, 0, PW, PH
picDither_Resize
HSDither_Change
BMIF24.biWidth = PW
BMIF24.biHeight = PH
BMIFPAL.biWidth = PW
BMIFPAL.biHeight = PH
DrawProgress 0
If ChRandomPalette.Value Then
    Dim I&
    For I = 0 To UBound(BMIFPAL.Palette)
      BMIFPAL.Palette(I).R = Rnd * 255
      BMIFPAL.Palette(I).G = Rnd * 255
      BMIFPAL.Palette(I).B = Rnd * 255
    Next
Else
    If CreateOctreePaletteFromHBITMAP(hDC, Picture.Handle, PW, PH, COLORS_MAX, COLORS_BITS, BMIFPAL.Palette(0)) = 0 Then
      MsgBox "生成调色板失败。", vbExclamation
    End If
End If
DrawPal
cmdDither.Enabled = True
OpSrcPic.Enabled = False
OpDitherPic.Enabled = False
OpSrcPic.Value = True
Exit Sub
eHandlr:
MsgBox Err.Description, vbExclamation, "出错"
End Sub

Sub DrawPal()
Dim X&, Y&
Dim DrX&, DrY&
Dim I&
For Y = 0 To 15
    DrX = 0
    For X = 0 To 15
      picPal.Line (DrX, DrY)-(DrX + 4, DrY + 4), RGB(BMIFPAL.Palette(I).R, BMIFPAL.Palette(I).G, BMIFPAL.Palette(I).B), BF
      I = I + 1
      If I >= COLORS_MAX Then Exit Sub
      DrX = DrX + 5
    Next
    DrY = DrY + 5
Next
End Sub

Private Sub Form_Resize()
On Error Resume Next
picDither.Width = picRightPanel.Left
End Sub

Private Sub Form_Unload(Cancel As Integer)
QuitLoop = True
End
End Sub

Private Sub HSDither_Change()
On Error Resume Next
Dim LeftBegin As Long
LeftBegin = -HSDither.Value
picColor1.Left = LeftBegin + PW * 0
picColor2.Left = LeftBegin + PW * 1
picColor3.Left = LeftBegin + PW * 2
picColor4.Left = LeftBegin + PW * 3
picResult.Left = LeftBegin + PW * 4
End Sub

Private Sub HSDither_Scroll()
HSDither_Change
End Sub

Private Sub OpDitherPic_Click()
picSrcPic.Visible = False
picDither.Visible = True
End Sub

Private Sub OpSrcPic_Click()
picSrcPic.Visible = True
picDither.Visible = False
End Sub

Private Sub picDither_Resize()
On Error Resume Next
Dim PP1W As Long, PP1H As Long, HSMax As Long
PP1W = picDither.ScaleWidth
PP1H = picDither.ScaleHeight
HSDither.Move 0, PP1H - 17, PP1W, 17
HSMax = PW * 5 - PP1W
If HSMax > 0 Then
    HSDither.Max = HSMax
    HSDither.LargeChange = PP1W
    HSDither.Visible = True
Else
    HSDither.Value = 0
    HSDither.Visible = False
End If
End SubBin下载:
Src下载:

0xAA55 发表于 2014-6-20 20:56:04

Bin的用法忘了补充了。
1、打开EXE
2、把图片拖进去
3、点“抖动按钮”,然后留意底部的滚动条!

KxIX 发表于 2014-6-21 02:20:25

0xAA55 发表于 2014-6-21 19:57:49

異界型丘比 发表于 2014-6-20 18:20
对我来说可能没什么意义 我基本都是保存PNG的

对我来说,这东西可以做成KKND2游戏的新版地图编辑器。

guodanpier2019 发表于 2019-9-15 13:30:44

大佬,这个工具生成的256色位图可以把颜色保留的和原图接近吗?

guodanpier2019 发表于 2019-9-15 13:35:47

谢谢啦,效果非常不错嘛

0xAA55 发表于 2019-9-17 11:33:45

guodanpier2019 发表于 2019-9-15 13:35
谢谢啦,效果非常不错嘛

这个抖动算法的效果并不好,请参阅论坛里的其它帖子
页: [1]
查看完整版本: 【图像】抖动算法实现真彩色图片高细节256色降级处理【旧帖,效果不好,勿用】