Converting 24BPP to 4BPP with GDI+ in VB.NET
我的vb.net程序当前将4bpp tiff作为位图,将其转换为图形,添加一些文本字符串,然后再次将其保存为tiff文件。默认情况下,输出bitmap.save()tiff文件似乎是24bpp(与输入无关),并且比原始tiff大很多。
是否可以保持与输出输入相同的4bpp调色板编码,如果不能,如何将我的位图像素格式从24bpp转换为4bpp索引。
我在http://www.bobpowell.net/onebit.htm上看到了一个使用bitmapdata.lockbits()将24 bpp转换为1 bpp的示例,但无法确定如何对4bpp进行转换。
非常感谢克里斯
这是我在这里发布的类的修改版本。它使用源站点注释中的4bpp逻辑。
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 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 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | Public Class BitmapEncoder ''' <summary> ''' Copies a bitmap into a 1bpp/4bpp/8bpp bitmap of the same dimensions, fast ''' </summary> ''' <param name="b">original bitmap</param> ''' <param name="bpp">1 or 8, target bpp</param> ''' <returns>a 1bpp copy of the bitmap</returns> Public Shared Function ConvertBitmapToSpecified(ByVal b As System.Drawing.Bitmap, ByVal bpp As Integer) As System.Drawing.Bitmap Select Case bpp Case 1 Case 4 Case 8 Case Else Throw New ArgumentException("bpp must be 1, 4 or 8") End Select ' Plan: built into Windows GDI is the ability to convert ' bitmaps from one format to another. Most of the time, this ' job is actually done by the graphics hardware accelerator card ' and so is extremely fast. The rest of the time, the job is done by ' very fast native code. ' We will call into this GDI functionality from C#. Our plan: ' (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed) ' (2) Create a GDI monochrome hbitmap ' (3) Use GDI"BitBlt" function to copy from hbitmap into monochrome (as above) ' (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed) Dim w As Integer = b.Width, h As Integer = b.Height Dim hbm As IntPtr = b.GetHbitmap() ' this is step (1) ' ' Step (2): create the monochrome bitmap. '"BITMAPINFO" is an interop-struct which we define below. ' In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs Dim bmi As New BITMAPINFO() bmi.biSize = 40 ' the size of the BITMAPHEADERINFO struct bmi.biWidth = w bmi.biHeight = h bmi.biPlanes = 1 '"planes" are confusing. We always use just 1. Read MSDN for more info. bmi.biBitCount = CShort(bpp) ' ie. 1bpp or 8bpp bmi.biCompression = BI_RGB ' ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes bmi.biSizeImage = CUInt((((w + 7) And &HFFFFFFF8) * h / 8)) bmi.biXPelsPerMeter = 1000000 ' not really important bmi.biYPelsPerMeter = 1000000 ' not really important ' Now for the colour table. Dim ncols As UInteger = CUInt(1) << bpp ' 2 colours for 1bpp; 256 colours for 8bpp bmi.biClrUsed = ncols bmi.biClrImportant = ncols bmi.cols = New UInteger(255) {} ' The structure always has fixed size 256, even if we end up using fewer colours If bpp = 1 Then bmi.cols(0) = MAKERGB(0, 0, 0) bmi.cols(1) = MAKERGB(255, 255, 255) ElseIf bpp = 4 Then bmi.biClrUsed = 16 bmi.biClrImportant = 16 Dim colv1 As Integer() = New Integer(15) {8, 24, 38, 56, 72, 88, 104, 120, 136, 152, 168, 184, 210, 216, 232, 248} For i As Integer = 0 To 15 bmi.cols(i) = MAKERGB(colv1(i), colv1(i), colv1(i)) Next ElseIf bpp = 8 Then For i As Integer = 0 To ncols - 1 bmi.cols(i) = MAKERGB(i, i, i) Next End If ' For 8bpp we've created an palette with just greyscale colours. ' You can set up any palette you want here. Here are some possibilities: ' greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i); ' rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255}; ' for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]); ' optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization ' ' Now create the indexed bitmap"hbm0" Dim bits0 As IntPtr ' not used for our purposes. It returns a pointer to the raw bits that make up the bitmap. Dim hbm0 As IntPtr = CreateDIBSection(IntPtr.Zero, bmi, DIB_RGB_COLORS, bits0, IntPtr.Zero, 0) ' ' Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap ' GDI programming is kind of confusing... nb. The GDI equivalent of"Graphics" is called a"DC". Dim sdc As IntPtr = GetDC(IntPtr.Zero) ' First we obtain the DC for the screen ' Next, create a DC for the original hbitmap Dim hdc As IntPtr = CreateCompatibleDC(sdc) SelectObject(hdc, hbm) ' and create a DC for the monochrome hbitmap Dim hdc0 As IntPtr = CreateCompatibleDC(sdc) SelectObject(hdc0, hbm0) ' Now we can do the BitBlt: BitBlt(hdc0, 0, 0, w, h, hdc, _ 0, 0, SRCCOPY) ' Step (4): convert this monochrome hbitmap back into a Bitmap: Dim b0 As System.Drawing.Bitmap = System.Drawing.Bitmap.FromHbitmap(hbm0) ' ' Finally some cleanup. DeleteDC(hdc) DeleteDC(hdc0) ReleaseDC(IntPtr.Zero, sdc) DeleteObject(hbm) DeleteObject(hbm0) ' Return b0 End Function Private Shared SRCCOPY As Integer = &HCC0020 Private Shared BI_RGB As UInteger = 0 Private Shared DIB_RGB_COLORS As UInteger = 0 <System.Runtime.InteropServices.DllImport("gdi32.dll")> _ Private Shared Function DeleteObject(ByVal hObject As IntPtr) As Boolean End Function <System.Runtime.InteropServices.DllImport("user32.dll")> _ Private Shared Function GetDC(ByVal hwnd As IntPtr) As IntPtr End Function <System.Runtime.InteropServices.DllImport("gdi32.dll")> _ Private Shared Function CreateCompatibleDC(ByVal hdc As IntPtr) As IntPtr End Function <System.Runtime.InteropServices.DllImport("user32.dll")> _ Private Shared Function ReleaseDC(ByVal hwnd As IntPtr, ByVal hdc As IntPtr) As Integer End Function <System.Runtime.InteropServices.DllImport("gdi32.dll")> _ Private Shared Function DeleteDC(ByVal hdc As IntPtr) As Integer End Function <System.Runtime.InteropServices.DllImport("gdi32.dll")> _ Private Shared Function SelectObject(ByVal hdc As IntPtr, ByVal hgdiobj As IntPtr) As IntPtr End Function <System.Runtime.InteropServices.DllImport("gdi32.dll")> _ Private Shared Function BitBlt(ByVal hdcDst As IntPtr, ByVal xDst As Integer, ByVal yDst As Integer, ByVal w As Integer, ByVal h As Integer, ByVal hdcSrc As IntPtr, _ ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal rop As Integer) As Integer End Function <System.Runtime.InteropServices.DllImport("gdi32.dll")> _ Private Shared Function CreateDIBSection(ByVal hdc As IntPtr, ByRef bmi As BITMAPINFO, ByVal Usage As UInteger, ByRef bits As IntPtr, ByVal hSection As IntPtr, ByVal dwOffset As UInteger) As IntPtr End Function <System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)> _ Private Structure BITMAPINFO Public biSize As UInteger Public biWidth As Integer, biHeight As Integer Public biPlanes As Short, biBitCount As Short Public biCompression As UInteger, biSizeImage As UInteger Public biXPelsPerMeter As Integer, biYPelsPerMeter As Integer Public biClrUsed As UInteger, biClrImportant As UInteger <System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=256)> _ Public cols As UInteger() End Structure Private Shared Function MAKERGB(ByVal r As Integer, ByVal g As Integer, ByVal b As Integer) As UInteger Return CUInt((b And 255)) Or CUInt(((r And 255) << 8)) Or CUInt(((g And 255) << 16)) End Function Private Sub New() End Sub End Class |
要使用它:
1 2 3 4 5 6 7 8 9 10 11 | 'Load your image Using B As New Bitmap("c:\test.tiff") 'Do a bunch of stuff to it '...' 'Convert it to 4BPP Using I = BitmapEncoder.ConvertBitmapToSpecified(B, 4) 'Save to disk I.Save("c:\test2.tiff") End Using End Using |
号