From: Tom Shelton on
["Followup-To:" header set to microsoft.public.vb.general.discussion.]
On 2009-10-01, Mike Williams <Mike(a)WhiskyAndCoke.com> wrote:
> "Tom Shelton" <tom_shelton(a)comcastXXXXXXX.net> wrote in message
> news:uM481OhQKHA.4692(a)TK2MSFTNGP06.phx.gbl...
>
>> Well... lol. i just did it a few minutes ago - but I did
>> it in C#. I used the Creek.jpg from my Pictures/Sample
>> Pictures folder. Also, it is a console app. So here is
>> the code:
>
> Okay. C# code wasn't the challenge though, so let me know when you've done
> it in VB.Net using a standard 24 bit .bmp file as stated in my original
> post.
>
> Mike

Fine... Here is the code - still using creek.jpg. Send me the 24-bit map you
would like to use and i'll use it. I can't find one that size and resolution
on my system. Only jpg's - but, from my perspective it doesn't matter the
code is the same.

Option Explicit On
Option Strict On

Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

Module Module1

Sub Main()
Dim iterations As Integer = 10
Dim totalTime As Integer = 0

For i As Integer = 1 To iterations
totalTime += CountColors()
Next

Console.WriteLine("Average Time: {0}", New TimeSpan(0, 0, 0, 0, CInt(totalTime / iterations)))
End Sub

Function CountColors() As Integer
Dim colors As New Dictionary(Of Integer, Integer)

Dim startTime As Integer = Environment.TickCount
Using bmp As New Bitmap("creek.jpg")
Dim bmd As BitmapData = bmp.LockBits(New Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat)
Dim byteCount As Integer = bmd.Stride * bmp.Height
Dim bytes(byteCount - 1) As Byte
Marshal.Copy(bmd.Scan0, bytes, 0, byteCount)
bmp.UnlockBits(bmd)

For i As Integer = 0 To byteCount - 1 Step 3
Dim color As Integer = BitConverter.ToInt32(New Byte() {bytes(i), bytes(i + 1), bytes(i + 2), 0}, 0)
If Not colors.ContainsKey(color) Then
colors.Add(color, color)
End If
Next

End Using
Dim total As Integer = Environment.TickCount - startTime
Console.WriteLine("Counted {0} unique colors in {1}", colors.Keys.Count, New TimeSpan(0, 0, 0, 0, total))
Return total
End Function
End Module

Output:

Counted 131763 unique colors in 00:00:00.1410000
Counted 131763 unique colors in 00:00:00.1250000
Counted 131763 unique colors in 00:00:00.1090000
Counted 131763 unique colors in 00:00:00.1100000
Counted 131763 unique colors in 00:00:00.1090000
Counted 131763 unique colors in 00:00:00.1090000
Counted 131763 unique colors in 00:00:00.1100000
Counted 131763 unique colors in 00:00:00.1090000
Counted 131763 unique colors in 00:00:00.1560000
Counted 131763 unique colors in 00:00:00.1100000
Average Time: 00:00:00.1190000
Press any key to continue . . .

--
Tom Shelton
From: Mike Williams on
"Tom Shelton" <tom_shelton(a)comcastXXXXXXX.net> wrote in message
news:u9KwLvpQKHA.1372(a)TK2MSFTNGP02.phx.gbl...

> Fine... Here is the code - still using creek.jpg. Send me the
> 24-bit map you would like to use and i'll use it. I can't find
> one that size and resolution on my system. Only jpg's - but,
> from my perspective it doesn't matter the code is the same.

[That was odd, the message I just posted seems to have gone astray from my
sent items. Here it is again just in case]

It doesn't need to be any specific size, I just suggested 1024 x 768 as an
example. If you wish to do so then you can load your jpg into MS Paint and
save it as a full colour 24 bit .bmp file. One of the reasons I specified a
..bmp file is the fact that the standard native VB6 methods of loading a jpg
will affect the colours of the resultant bitmap if loaded on a machine that
happens to be running at a lower than full colour depth (16 bits or
whatever) and so in VB6 if you want to avoid that it would be necessary to
add code to load a jpg in a different way, something for which I usally use
the Intel jpg library. The thing is (referring back to the other discussion
about the mandelbrot) it seems that something about my machine is causing
your own VB.Net code to run at the same speed, whether I run your completely
unmodified mandelbrot.exe file in your Release folder or whether I load the
project into VB Express and run it in Debug mode. In both cases when run
full screen I get about 9 to 10 seconds, which according to what you have
said appears to mean that there is something odd about the way my machine
runs your own mandelbrot.exe file in your Bin / Release folder. Therefore, I
am not sure that I will be seeing the correct timing from your count the
colours VB.Net code on this machine. Anyway, for what it's worth, the time
I'm getting from your own VB.Net code (the code you just posted) when using
it to count the colors in a 1024 x 768 pixel bmp file is very similar to
your own reported timings at your end using your creek.jpg image file. The
timings for your VB.Net code on my machine for the 1024x768 pixel bmp file I
am using varies between 0.094 and 0.109 seconds.

When I run my own VB6 code which is capable of dealing with either bmps or
jpgs and which uses the relatively slow native VB method of loading a jpg I
get about 0.062 to 0.064 to count the colours in a 1024 x 768 pixel jpg
(although that can be improved considerably by using the Intel jpg library
or somehting similar). If I instead use my VB6 code which is currently
optimised for dealing with bmp files (completely different code to the bmp
or jpg code I've just mentioned) I get between 0.014 and 0.016 to count the
colours in a 1024 x 768 pixel bmp. This is very fast, but it would of course
be slower when the code to deal with a jpg is added (typically I would use
the Intel jpg library which would add probably something in the region of
about a further 0.015 seconds).

Anyway, perhaps it might be best if you tested both your own VB.Net code and
my VB6 code on your own machine so that you can check out the timings for
yourself. The code below is my optimised for bmp files code (the one
producing a time of between 0.014 and 0.016 seconds on my own machine to
count the colours in a 1024 x 768 pixel .bmp file) so until I get around to
adding some fast jpg functionality you will need to run it on a .bmp file.
It would be interesting to see what timings you get at your end. You will of
course need to run it as a native code compiled exe because whereas you can
do the job in about a dozen or so lines of code in VB6 I have optimised it
by using masses of extra code to give me more flexible data handling, which
improves the speed as a compiled exe but which completely destroys the speed
when run in the IDE (because of all the dozens of extra lines of code, which
are not a problem when compiled but which slow down the code in the IDE). I
make no apologies for the rambling state of the code, because I wrote it
fairly quickly for this test and I concentrated totally on speed of
execution at the expense of readworthy or small or neat code. It actually
looks a complete mess at the moment, and there are dozens and dozens of
lines of code (maybe hundreds!) that would not otherwise be needed if I
hadn't wanted to optimise it to get the sort of data pointers I wanted, and
various parts which use a number of almost identical small blocks instead of
calls to a suitable function for similar reasons. As I've said, it is an
absolute mess, but it works okay ;-)

This is my VB6 code that is currently giving me a time of between 0.014 and
0.016 seconds to count the colours on a 1024 x 768 pixel .bmp file. It would
be interesting to see what timings it returns your own machine, and the size
of image file you are using. If you don't want to mess about loading a jpg
into MS Paint and saving it as a 24 bit .bmp file then let me know and I'll
send you the .bmp file I'm using myself. Anyway, paste the code into a new
VB6 project (one Form containing a Command Button) and compile to a native
code exe.

Mike

Option Explicit
Private Declare Function LoadImage Lib "user32" Alias _
"LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, _
ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, _
ByVal un2 As Long) As Long
Private Declare Function GetObject Lib "gdi32" _
Alias "GetObjectA" (ByVal hObject As Long, _
ByVal nCount As Long, lpObject As Any) As Long
Private Declare Function DeleteObject Lib "gdi32" _
(ByVal hObject As Long) As Long
Private Declare Function VarPtrArray Lib "msvbvm60.dll" _
Alias "VarPtr" (Ptr() As Any) As Long
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, _
ByVal ByteLen As Long)
Private Const IMAGE_BITMAP = 0
Private Const LR_LOADFROMFILE = &H10
Private Const LR_CREATEDIBSECTION As Long = &H2000
Private Const LR_VGACOLOR As Long = &H80
Private Type SAFEARRAY1D
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
cElements As Long
lLbound As Long
End Type
Private Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
Private SourceWidth As Long, SourceHeight As Long
Private clrs(0 To 2 ^ 21 - 1) As Byte
Private TwoToThePowerOf(0 To 7) As Byte
Private Declare Function timeGetTime _
Lib "winmm.dll" () As Long
Private Declare Function timeBeginPeriod _
Lib "winmm.dll" (ByVal uPeriod As Long) As Long
Private Declare Function timeEndPeriod _
Lib "winmm.dll" (ByVal uPeriod As Long) As Long

Private Sub Form_Load()
timeBeginPeriod 1
Dim n As Long
For n = 0 To 7
TwoToThePowerOf(n) = 2 ^ n
Next n
End Sub

Private Function CountColours(bmpFile As String) As Long
Dim t1 As Long, t2 As Long, n As Long
Dim j As Long, p As Long, used As Long
Dim allclrsByte As Long, bitmask As Byte
Dim myDIB As Long, bmpInf As BITMAP
Dim sa1 As SAFEARRAY1D, sa2 As SAFEARRAY1D
Dim sa3 As SAFEARRAY1D, sa4 As SAFEARRAY1D
Dim sa5 As SAFEARRAY1D, bytePos As Long
Dim SourceArray1() As Long, SourceArray2() As Long
Dim SourceArray3() As Long, SourceArray4() As Long
Dim sourcearray5() As Byte, done As Boolean
Dim z As Byte, scanline As Long, LastLine As Boolean
Dim bmpLongs As Long, oddPixels As Long, scanLongs As Long
Dim twelves As Long
Dim fours As Long, offset As Long
myDIB = LoadImage(App.hInstance, bmpFile, _
IMAGE_BITMAP, 0, 0, _
LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
GetObject myDIB, Len(bmpInf), bmpInf
If myDIB = 0 Or bmpInf.bmBitsPixel <> 24 Then
MsgBox "Not a valid full colour 24 bit .bmp file"
Exit Function
End If
Erase clrs()
GetObject myDIB, Len(bmpInf), bmpInf
SourceWidth = bmpInf.bmWidth
SourceHeight = bmpInf.bmHeight
bmpLongs = (bmpInf.bmWidthBytes \ 4) * SourceHeight
twelves = ((SourceWidth * 3) \ 12)
fours = twelves * 3
scanLongs = bmpInf.bmWidthBytes \ 4
oddPixels = SourceWidth - twelves * 4
On Error GoTo finish
With sa1
.cDims = 1
.cbElements = 4
.lLbound = 0
.cElements = bmpLongs
.pvData = bmpInf.bmBits
End With
LSet sa2 = sa1: sa2.pvData = sa1.pvData + 3
LSet sa3 = sa1: sa3.pvData = sa1.pvData + 6
LSet sa4 = sa1: sa4.pvData = sa1.pvData + 9
CopyMemory ByVal VarPtrArray(SourceArray1), VarPtr(sa1), 4
CopyMemory ByVal VarPtrArray(SourceArray2), VarPtr(sa2), 4
CopyMemory ByVal VarPtrArray(SourceArray3), VarPtr(sa3), 4
CopyMemory ByVal VarPtrArray(SourceArray4), VarPtr(sa4), 4
LSet sa5 = sa1: sa5.cbElements = 1: sa5.cElements = bmpLongs * 4
CopyMemory ByVal VarPtrArray(sourcearray5), VarPtr(sa5), 4
For scanline = 0 To SourceHeight - 1
If scanline = (SourceHeight - 1) Then
LastLine = True
End If
For j = 0 To fours - 3 Step 3
p = SourceArray1(j + offset) And &HFFFFFF
allclrsByte = p \ 8
bitmask = TwoToThePowerOf(7 - p Mod 8)
z = clrs(allclrsByte)
If (z And bitmask) = 0 Then
used = used + 1
clrs(allclrsByte) = z Or bitmask
End If
p = SourceArray2(j + offset) And &HFFFFFF
allclrsByte = p \ 8
bitmask = TwoToThePowerOf(7 - p Mod 8)
z = clrs(allclrsByte)
If (z And bitmask) = 0 Then
used = used + 1
clrs(allclrsByte) = z Or bitmask
End If
p = SourceArray3(j + offset) And &HFFFFFF
allclrsByte = p \ 8
bitmask = TwoToThePowerOf(7 - p Mod 8)
z = clrs(allclrsByte)
If (z And bitmask) = 0 Then
used = used + 1
clrs(allclrsByte) = z Or bitmask
End If
If LastLine = True And j = (fours - 3) Then
bytePos = (j + offset) * 4 + 9
p = &H10000 * sourcearray5(bytePos + 2) _
+ &H100& * sourcearray5(bytePos + 1) _
+ sourcearray5(bytePos)
allclrsByte = p \ 8
bitmask = TwoToThePowerOf(7 - p Mod 8)
z = clrs(allclrsByte)
If (z And bitmask) = 0 Then
used = used + 1
clrs(allclrsByte) = z Or bitmask
End If
Else
p = SourceArray4(j + offset) And &HFFFFFF
allclrsByte = p \ 8
bitmask = TwoToThePowerOf(7 - p Mod 8)
z = clrs(allclrsByte)
If (z And bitmask) = 0 Then
used = used + 1
clrs(allclrsByte) = z Or bitmask
End If
End If
If oddPixels > 0 Then
bytePos = (j + offset + 3) * 4
For n = 0 To oddPixels - 1
p = &H10000 * sourcearray5(bytePos + 2) _
+ &H100& * sourcearray5(bytePos + 1) _
+ sourcearray5(bytePos)
allclrsByte = p \ 8
bitmask = TwoToThePowerOf(7 - p Mod 8)
z = clrs(allclrsByte)
If (z And bitmask) = 0 Then
used = used + 1
clrs(allclrsByte) = z Or bitmask
End If
bytePos = bytePos + 3
Next n
End If
Next j
offset = offset + scanLongs
Next scanline
CountColours = used: done = True
finish:
CopyMemory ByVal VarPtrArray(SourceArray1), 0&, 4
CopyMemory ByVal VarPtrArray(SourceArray2), 0&, 4
CopyMemory ByVal VarPtrArray(SourceArray3), 0&, 4
CopyMemory ByVal VarPtrArray(SourceArray4), 0&, 4
CopyMemory ByVal VarPtrArray(sourcearray5), 0&, 4
DeleteObject myDIB
If Not done Then CountColours = 0
End Function

Private Sub Command1_Click()
Dim tStart As Long, tFinish As Long
Dim s1 As String, clrs As Long
tStart = timeGetTime
clrs = CountColours("c:\temp\1024x768.bmp")
tFinish = timeGetTime
If clrs > 0 Then
s1 = Format(SourceWidth) & " x " & Format(SourceHeight) _
& " pixel bmp file containing " & Format(clrs) _
& " unique colours (" & Format _
((tFinish - tStart) / 1000, "0.000") & " seconds)"
MsgBox s1
Else
MsgBox "Error dealing with bmp file"
End If
End Sub

' *** I told you it was a mess! ***





From: Tom Shelton on
["Followup-To:" header set to microsoft.public.vb.general.discussion.]
On 2009-10-01, Mike Williams <Mike(a)WhiskyAndCoke.com> wrote:
> "Tom Shelton" <tom_shelton(a)comcastXXXXXXX.net> wrote in message
> news:u9KwLvpQKHA.1372(a)TK2MSFTNGP02.phx.gbl...
>
>> Fine... Here is the code - still using creek.jpg. Send me the
>> 24-bit map you would like to use and i'll use it. I can't find
>> one that size and resolution on my system. Only jpg's - but,
>> from my perspective it doesn't matter the code is the same.
>
> [That was odd, the message I just posted seems to have gone astray from my
> sent items. Here it is again just in case]
>
> It doesn't need to be any specific size, I just suggested 1024 x 768 as an
> example. If you wish to do so then you can load your jpg into MS Paint and
> save it as a full colour 24 bit .bmp file. One of the reasons I specified a
> .bmp file is the fact that the standard native VB6 methods of loading a jpg
> will affect the colours of the resultant bitmap if loaded on a machine that
> happens to be running at a lower than full colour depth (16 bits or
> whatever) and so in VB6 if you want to avoid that it would be necessary to
> add code to load a jpg in a different way, something for which I usally use
> the Intel jpg library. The thing is (referring back to the other discussion
> about the mandelbrot) it seems that something about my machine is causing
> your own VB.Net code to run at the same speed, whether I run your completely
> unmodified mandelbrot.exe file in your Release folder or whether I load the
> project into VB Express and run it in Debug mode. In both cases when run
> full screen I get about 9 to 10 seconds, which according to what you have
> said appears to mean that there is something odd about the way my machine
> runs your own mandelbrot.exe file in your Bin / Release folder. Therefore, I
> am not sure that I will be seeing the correct timing from your count the
> colours VB.Net code on this machine. Anyway, for what it's worth, the time
> I'm getting from your own VB.Net code (the code you just posted) when using
> it to count the colors in a 1024 x 768 pixel bmp file is very similar to
> your own reported timings at your end using your creek.jpg image file. The
> timings for your VB.Net code on my machine for the 1024x768 pixel bmp file I
> am using varies between 0.094 and 0.109 seconds.
>
> When I run my own VB6 code which is capable of dealing with either bmps or
> jpgs and which uses the relatively slow native VB method of loading a jpg I
> get about 0.062 to 0.064 to count the colours in a 1024 x 768 pixel jpg

Changing it to an actual bitmap (not sure why I didn't think of using paint to
convert it - duh!) moves the timing to around .07 - .083. So,
the difference must be the jpg encoder overhead - though, it didn't change the
code. And the C# version is actually a tad faster, running around .068 -
..078.

So, what do I conclude - that for a lot less and easier code... That I'm
loosing fractions of a second in performance - not even enough for anyone to
ever notice. I think I'm ok with that. It lets me spend more time with the
creative parts of my apps...

If you want me to run your app, you'll need to email me the binary - my only
box with vb6 installed is dead, and I'm not planning to install it on this
one.

--
Tom Shelton
From: Mike Williams on
"Tom Shelton" <tom_shelton(a)comcastXXXXXXX.net> wrote in message
news:OIIX2NtQKHA.5032(a)TK2MSFTNGP05.phx.gbl...

> Changing it to an actual bitmap (not sure why I didn't think of
> using paint to convert it - duh!) moves the timing to around
> .07 - .083. So, the difference must be the jpg encoder
> overhead - though, it didn't change the code. And the C#
> version is actually a tad faster, running around .068 -.078.
> So, what do I conclude - that for a lot less and easier code...
> That I'm loosing fractions of a second in performance - not
> even enough for anyone to ever notice.

Don't forget that the .062 time I mentioned for VB6 to count the number of
unique colours was for a .jpg loaded using the relatively slow LoadPicture
method, whereas the VB6 code I posted (which currently deals only with .bmps
and which does not use that method) takes just .014 to .016 seconds (against
the .070 to .083 you report for your VB.Net code when that too is working
with a .bmp), so the VB6 code is considerably faster than the VB.Net code.
It will take a bit longer when proper jpg handling is added (about 0.015
seconds extra), but that is still considerably faster than the VB.Net code.
Not that it really matters of course, it's not that important and I wouldn't
have bothered were it not for the that I wanted to show that Cor Ligthert
was massively exaggerating when he said that VB.Net code runs very much
faster than VB6 code, whereas in this specific case it is the other way
round.

> I think I'm ok with that. It lets me spend more time with the
> creative parts of my apps...If you want me to run your app,
> you'll need to email me the binary - my only box with vb6
> installed is dead, and I'm not planning to install it on this one.

Okay. I'll send you a compiled exe via email (I don't have a suitable web
page to host it at the moment).

Mike


From: Tom Shelton on
On 2009-10-01, Mike Williams <Mike(a)WhiskyAndCoke.com> wrote:
> "Tom Shelton" <tom_shelton(a)comcastXXXXXXX.net> wrote in message
> news:OIIX2NtQKHA.5032(a)TK2MSFTNGP05.phx.gbl...
>
>> Changing it to an actual bitmap (not sure why I didn't think of
>> using paint to convert it - duh!) moves the timing to around
>> .07 - .083. So, the difference must be the jpg encoder
>> overhead - though, it didn't change the code. And the C#
>> version is actually a tad faster, running around .068 -.078.
>> So, what do I conclude - that for a lot less and easier code...
>> That I'm loosing fractions of a second in performance - not
>> even enough for anyone to ever notice.
>
> Don't forget that the .062 time I mentioned for VB6 to count the number of
> unique colours was for a .jpg loaded using the relatively slow LoadPicture
> method, whereas the VB6 code I posted (which currently deals only with .bmps
> and which does not use that method) takes just .014 to .016 seconds (against
> the .070 to .083 you report for your VB.Net code when that too is working
> with a .bmp), so the VB6 code is considerably faster than the VB.Net code.
> It will take a bit longer when proper jpg handling is added (about 0.015
> seconds extra), but that is still considerably faster than the VB.Net code.
> Not that it really matters of course, it's not that important and I wouldn't
> have bothered were it not for the that I wanted to show that Cor Ligthert
> was massively exaggerating when he said that VB.Net code runs very much
> faster than VB6 code, whereas in this specific case it is the other way
> round.
>

Well, Mike - I'm certainly no expert on this stuff. There may be much, much
better ways to accomplish this task. But, again, the preformance difference
is in factions of a second. Not even worth talking about outside of news
groups. And, Mike, mine already handles jpg. I don't even have to change code.

It would be trivial really to just read the bitmap data directly from the
bitmap file, rather then use the bitmap class, etc - but the bitmap class can
handle bmp, gif, png, etc, etc.

Like with most things - if speed is unacceptable, then I find the bottle neck
and then see if there is a faster way to accomplish it. I agree that people
shouldn't be making all kinds of speed claims - but, it goes both ways.


>> I think I'm ok with that. It lets me spend more time with the
>> creative parts of my apps...If you want me to run your app,
>> you'll need to email me the binary - my only box with vb6
>> installed is dead, and I'm not planning to install it on this one.
>
> Okay. I'll send you a compiled exe via email (I don't have a suitable web
> page to host it at the moment).

It errors out on the converted bitmap, telling me it's not a valid 24-bit
bitmap. But, according to vista it is - and the bitmap class says it is.
Maybe you can now send me your test image, and then I can run it agains
something you know works?

--
Tom Shelton