Reputation: 2455
I have a class that I had written a long time ago in VB.net. I want to use the class in C#, so to save time I thought I would just convert it to a class library. I'm running into some confusion in doing this though. When I instantiate my original class, I would use the System.Drawing.Save() method to save an image file. See below imageConv.Save(filePath)
.
Imports System.Drawing
...
...
''' <summary>
''' Convert the image to a smaller more manageable 4-bit png
''' </summary>
Private Shared Function ConvertImage(filepath As String) As String
Using image As New Bitmap(filepath)
'Do Stuff
'Convert it to 4BPP
Using imageConv = BitmapEncoder.Convert(image, 4)
'Update the filepath for saving
filepath = Path.GetTempPath & "image.png"
'Save to disk
imageConv.Save(filepath)
End Using
End Using
ConvertImage = filepath
End Function
Now when I copied the class into a class library project, added the reference in my C# project, and instantiated the class, I can no longer use the Save method.
private static void compressBitmap(ImageData imgData)
{
using (Bitmap imageRaw = new Bitmap(imgData.bmpFilePath))
{
using (ConvertBitmap imageCompressed = new ConvertBitmap())
{
imageCompressed.Convert(imageRaw, 4);
//imageCompressed.Save(imgData.bmpFilePath);
}
}
}
What am I missing here? I know I need to instantiate ConvertBitmap imageCompressed = new ConvertBitmap.Convert(imageRaw, 4))
but it says 'Convert' does not exist in the type 'ConvertBitmap'. It definitely does. It's a public function.
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
''' <summary>
''' Copies a bitmap into a 1bpp/4bpp/8bpp bitmap of the same dimensions, fast
''' </summary>
Public Class ConvertBitmap : Implements IDisposable
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' Instantiate a SafeHandle instance.
Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
''' <summary>
''' Public implementation of Dispose pattern callable by consumers.
''' </summary>
Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
''' <summary>
''' Protected implementation of Dispose pattern.
''' </summary>
''' <param name="disposing"></param>
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Return
If disposing Then
handle.Dispose()
' Free any other managed objects here.
'
End If
disposed = True
End Sub
''' <param name="b">original bitmap</param>
''' <param name="bpp">1 or 8, target bpp</param>
''' <returns>a 1bpp copy of the bitmap</returns>
Public Function Convert(b As Bitmap, bpp As Integer) As 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 = _biRgb
' 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 = New Integer(15) {8, 24, 38, 56, 72, 88, 104, 120, 136, 152, 168, 184, 210, 216, 232, 248}
For i = 0 To 15
bmi.cols(i) = Makergb(colv1(i), colv1(i), colv1(i))
Next
ElseIf bpp = 8 Then
For i = 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, _dibRgbColors, 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 Bitmap = 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 _biRgb As UInteger = 0
Private Shared _dibRgbColors As UInteger = 0
<DllImport("gdi32.dll")>
Private Shared Function DeleteObject(hObject As IntPtr) As Boolean
End Function
<DllImport("user32.dll")>
Private Shared Function GetDC(hwnd As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll")>
Private Shared Function CreateCompatibleDC(hdc As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")>
Private Shared Function ReleaseDC(hwnd As IntPtr, hdc As IntPtr) As Integer
End Function
<DllImport("gdi32.dll")>
Private Shared Function DeleteDC(hdc As IntPtr) As Integer
End Function
<DllImport("gdi32.dll")>
Private Shared Function SelectObject(hdc As IntPtr, hgdiobj As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll")>
Private Shared Function BitBlt(hdcDst As IntPtr, xDst As Integer, yDst As Integer, w As Integer, h As Integer,
hdcSrc As IntPtr,
xSrc As Integer, ySrc As Integer, rop As Integer) As Integer
End Function
<DllImport("gdi32.dll")>
Private Shared Function CreateDIBSection(hdc As IntPtr, ByRef bmi As Bitmapinfo, usage As UInteger,
ByRef bits As IntPtr, hSection As IntPtr, dwOffset As UInteger) As IntPtr
End Function
<StructLayout(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
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=256)> Public cols As UInteger()
End Structure
Private Shared Function Makergb(r As Integer, g As Integer, b As Integer) As UInteger
Return CUInt((b And 255)) Or CUInt(((r And 255) << 8)) Or CUInt(((g And 255) << 16))
End Function
Public Sub New()
End Sub
End Class
Upvotes: 3
Views: 102
Reputation: 54417
The reason you get this error message:
'Convert' does not exist in the type 'ConvertBitmap'.
is that this line of code doesn't do what you think it does:
ConvertBitmap imageCompressed = new ConvertBitmap.Convert(imageRaw, 4))
That code is looking for a type named Convert
(NOT a method) declared inside the ConvertBitmap
class that has a constructor that that takes a Bitmap
and an int
as parameters. Obviously there is no such type.
What you actually need to do is create a ConvertBitmap
object, call its Convert
method to get a Bitmap
object, then call Save
on that:
private static void compressBitmap(ImageData imgData)
{
using (Bitmap imageRaw = new Bitmap(imgData.bmpFilePath))
{
using (ConvertBitmap imageCompressed = new ConvertBitmap())
{
imageCompressed.Convert(imageRaw, 4).Save(imgData.bmpFilePath);
}
}
}
Upvotes: 2