From: Ulrich Korndoerfer on
Hi,

charles schrieb:

> On 7 July, 14:35, "Larry Serflaten" <serfla...(a)gmail.com> wrote:
>
>> Did your examples access the image data from an array?
>> Did you see this?http://www.vbaccelerator.com/codelib/gfx/dibsect.htm
>
> Yes, and I also saw the following vbaccelerator code which uses a
> cDibSection Class to count the colors.
>
> http://www.vbaccelerator.com/home/vb/code/vbmedia/image_processing/Counting_Colours/Count_Colours_Sample.asp
>
> The problem is that it is quite slow, taking about two seconds for my
> sample 1600 x 1200 pixel .bmp file. It runs a bit faster on my other
> machine, about one and a half seconds, but it returns a completely
> different answer for exactly the same .bmp file! Is there a faster and
> more reliable way?

A faster way might be to make a copy of the pixel colors into a 1dim
array of longs. Then sort the array. After the array has been sorted,
counting the number of unique colors is easy and requires one run over
the sorted array.

How fast this method is depends strongly on the sorting algorithm. Quick
sort would be a good start. Even faster would be a counting sort.
However the counting sort needs an additional helper array, which in the
case of 24 bit color values has to have room for 24^2 entries. Such an
array using Long as item type would need about 64 MBytes of memory. If
you can afford that, go this route.

Because for an eg. 2 MPixel picture there are at most 2 M different
colors, and more likely the unique color count is much below, one could
use kind of a sparse array for the helper array the counting sort needs.
Either kind of hashing or a pseudo 2-dim array with entries created on
demand would help. eg:

Private Type TRedGreenEntry
UniqueCount As Long
Blue() As Long
End Type

'First fill PixelColors with the pixel colors!
'Then call UniqueColorCount

Private Function UniqueColorCount(ByRef PixelColors() As Long, _
ByVal PixelColorsCount As Long) _
As Long

Dim Helper() As TRedGreenEntry
Dim RGIdx As Long, BIdx As Long, i As Long

ReDim Helper(0 To 2^16-1)

'Do the counting sort

For i = 0 To PixelColorsCount - 1
RGidx = (PixelColors(i) And &HFFFF00) \ 256
BIdx = PixelColors(i) And &HFF

With Helper(RGIdx)
If .UniqueCount = 0 Then ReDim .Blue(0 To 255)
.Blue(BIdx) = .Blue(BIdx) + 1&
If .Blue(Idx) = 1& Then .UniqueCount = .UniqueCount + 1&
End With
Next i

'Now count the unique colors

For i = 0 To 2^16-1
UniqueColorCount = UniqueColorCount + Helper(i).UniqueCount
Next i

End Function

--
Ulrich Korndoerfer

VB tips, helpers, solutions -> http://www.prosource.de/Downloads/
MS Newsgruppen Alternativen -> http://www.prosource.de/ms-ng-umzug.html
From: Mike Williams on
"charles" <cbabbage59(a)yahoo.com> wrote in message
news:47917783-6326-457a-9dc2-974fd5edd24d(a)y4g2000yqy.googlegroups.com...

> In both cases it took about two seconds on one machine
> and about one and a half seconds on the other to count
> the colors. Do you have a faster way of doing it?

Here's an example that counts the colours very quickly. For best results you
should run it as a native code compiled exe, preferably using the Remove
Integer Overflow Checks compiler optimization. On my own machine it counts
the number of colours in a 1600 x 1200 pixel .bmp file in just over 30
milliseconds. This includes the time to load the file from disk, so it
should be a bit quicker if your bitmap is already in memory. Paste the
example into a VB Form containing a Command button and change the hard coded
picture path to a standard full colour .bmp file that exists on your own
machine. The example code as it stands deals only with standard .bmp image
files (because it uses the LoadImage function to load them into a
DIBSection) but you can use exactly the same counting technique on other
kinds of images as long as you add the required code to load them into a
DIBSection.

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 ' set timer resolution
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
' Note: This task can be done using just a very small
' fraction of the declarations and the code used here,
' but it is actually faster (at least in VB6) to do it
' this much more code heavy way.
' This original testbed code specifically for use on
' full colour .bmp files and should be run as a native
' code compiled exe).
Dim 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 ' number of blocks of 4 pixels (12 bytes)
Dim fours As Long, offset As Long
' Note: We can save about 20 milliseconds (on a 4800x3600 bmp)
' or about a millisecond or so on a 1024x768 bmp) by loading
' the data into a VB Byte array rather than using LoadImage.
' (Might do that later).
myDIB = LoadImage(0&, 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() ' clear the array that tracks each colour used
GetObject myDIB, Len(bmpInf), bmpInf
SourceWidth = bmpInf.bmWidth
SourceHeight = bmpInf.bmHeight
bmpLongs = (bmpInf.bmWidthBytes \ 4) * SourceHeight
' get data for a single scanline (first just 4 pixel blocks)
twelves = ((SourceWidth * 3) \ 12) ' blocks of 12 bytes
fours = twelves * 3 ' blocks of 4 bytes
scanLongs = bmpInf.bmWidthBytes \ 4
oddPixels = SourceWidth - twelves * 4
' Note: Instead of using a simple array of Longs on bitmap
' data returned by GetDIBits or a Byte array on the bitmap
' data directly using three Byte reads per pixel, this
' method points four separate arrays of Longs directly at
' slightly different positions in the same single block of
' bitmap data so that we can get at each pixel (3 bytes)
' with a single read. It's a bit unusual (a lot unusual)
' in that it reads Longs from the data at positions that
' do not start on a word boundary (the 3, 6, 9 stuff below)
' but it works very well and despite all the extra required
' code it is quite fast. This method uses a lot of code to
' do what would otherwise be a simple task, and so it is
' designed to run as a native code compiled exe (as is
' standard anyway with VB6) and such code will run very
' slowly in the VB6 IDE.
On Error GoTo finish
With sa1
.cDims = 1 ' one dimension
.cbElements = 4 ' four bytes per element (array of Longs)
.lLbound = 0 ' lBound
.cElements = bmpLongs ' number of elements
.pvData = bmpInf.bmBits ' point array1 (Longs) at DIB data byte zero
End With
' the following arrangement of SAFEARRAY structures complicates
' matters but it enables us to deal with most of the RGB pixel
' data of each horizontal scanline in groups of twelve bytes
' (4 pixels) addressing each set of pixel data as a Long, which
' speeds up the code (the remaining odd pixels of each scanline,
' those not in a group of 12 bytes, are dealt with separately)
LSet sa2 = sa1: sa2.pvData = sa1.pvData + 3 ' point array2 at byte 3
LSet sa3 = sa1: sa3.pvData = sa1.pvData + 6 ' point array3 at byte 6
LSet sa4 = sa1: sa4.pvData = sa1.pvData + 9 ' point array4 at byte 9
CopyMemory ByVal VarPtrArray(SourceArray1), VarPtr(sa1), 4 ' set up
CopyMemory ByVal VarPtrArray(SourceArray2), VarPtr(sa2), 4 ' arrays
CopyMemory ByVal VarPtrArray(SourceArray3), VarPtr(sa3), 4 ' as above
CopyMemory ByVal VarPtrArray(SourceArray4), VarPtr(sa4), 4
LSet sa5 = sa1: sa5.cbElements = 1: sa5.cElements = bmpLongs * 4
CopyMemory ByVal VarPtrArray(sourcearray5), VarPtr(sa5), 4
' j = red : j+1 = green : j+2 = blue
For scanline = 0 To SourceHeight - 1
If scanline = (SourceHeight - 1) Then
LastLine = True
End If
' The following block contains an identical code block
' repeated numerous times simply because for this
' type of code a call to a function would waste a lot
' of time. Also, it adds extra logic to a specific
' task that would otherwise be a simple one liner
' because it uses bits rather than bytes or booleans
' to store each used colour. This additional logic
' slows the code down, but the slowdown is more than
' offset by the speed gained from having a much
' smaller block of flag data to access so that the
' data is usually read more quickly
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
' Check if on last Long of last scanline because if we
' address that location as a Long using the method we
' are using to address the three bytes of data the
' last byte of the Long will be one byte outside the
' area containing the DIB, which would cause serious
' problems (we won't need to bother with this if we
' later change to loading the bmp file straight into
' a VB array instead of using the LoadImage GDI
' function because we can then load it into a slightly
' larger data area).
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
' Next j
If oddPixels > 0 Then
' here we need to do the odd pixels (not in groups
' of 12) at end of each scanline
' the number of of pixels is in the variable oddPixels
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 ' offset next scanline
Next scanline
CountColours = used: done = True
finish:
' release arrays
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, UniqueColours As Long
tStart = timeGetTime
UniqueColours = CountColours("c:\temp\jessica1.bmp")
tFinish = timeGetTime
If UniqueColours > 0 Then
s1 = Format(SourceWidth) & " x " & Format(SourceHeight) _
& " pixel bmp file containing " & Format(UniqueColours) _
& " unique colours (" & Format(tFinish - tStart, "0.0") _
& " milliseconds)"
MsgBox s1
Else
MsgBox "Error dealing with bmp file"
End If
End Sub






From: Karl E. Peterson on
After serious thinking Nobody wrote :
> Here is some code from one of my projects that calculates how many ones are
> there in a byte. For example, there are 3 ones in 00001110. This never
> change, so this can be put in a 256 element array and used to quickly count
> the number of ones in a byte. Here is the code:
<snip>
> For B = 0 To 255
> c = 0
> For i = 0 To 7
> If ((2 ^ i) And B) <> 0 Then
> c = c + 1
> End If
> Next
> NumberOfOnes(B) = c
> Next

In a case like that (or, more importantly, like the one being
discussed), I'd definitely urge precalculating the powers of two,
rather than repeatedly incurring the same exponentiations. Could be a
module level array, or a simple function (safer, but less efficient):

Private Function TwoToThe(ByVal Power As Long) As Long
Static BeenHere As Boolean
Static Results(0 To 31) As Long
Dim i As Long

' Build lookup table, first time through.
' Results hold powers of two from 0-31.
If Not BeenHere Then
For i = 0 To 30
Results(i) = 2 ^ i
Next i
Results(31) = &H80000000
BeenHere = True
End If

' Return requested result
If Power >= 0 And Power <= 31 Then
TwoToThe = Results(Power)
End If
End Function

--
..NET: It's About Trust!
http://vfred.mvps.org


From: charles on
On 7 July, 20:15, "Mike Williams" <M...(a)WhiskeyAndCoke.com> wrote:

> Here's an example that counts the colours very quickly.

Thanks Mike. Just what I needed. Very, very fast, and very accurate.
And it works correctly on both my machines. Thanks also to Dee Earley,
Nobody, Karl Peterson and Ulrich Korndoerfer for your help.

Charles

From: Ulrich Korndoerfer on
Hi,

charles schrieb:
> On 7 July, 20:15, "Mike Williams" <M...(a)WhiskeyAndCoke.com> wrote:
>
>> Here's an example that counts the colours very quickly.
>
> Thanks Mike. Just what I needed. Very, very fast, and very accurate.
> And it works correctly on both my machines. Thanks also to Dee Earley,
> Nobody, Karl Peterson and Ulrich Korndoerfer for your help.

Never mind. My example given was "top off the head", in the meanwhile I
got something better ;-)

When interested, see below.

'*******************************************************************************
'* Private API method declarations, types, consts
'*******************************************************************************

Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" _
(ByVal hInst As Long, _
ByVal lpszName As String, _
ByVal uType As Long, _
ByVal cxDesired As Long, _
ByVal cyDesired As Long, _
ByVal fuLoad As Long) As Long

Private Declare Function GetObject Lib "gdi32" Alias "GetObjectA" _
(ByVal hObject As Long, _
ByVal nCount As Long, _
ByRef 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" _
(ByRef Arr() As Any) As Long

Private Declare Sub PokeLong Lib "msvbvm60" Alias "PutMem4" _
(ByVal Addr As Long, _
ByVal Value As Long)

Private Const IMAGE_BITMAP = 0
Private Const LR_LOADFROMFILE = &H10
Private Const LR_CREATEDIBSECTION As Long = &H2000

Private Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long 'Scan line width
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type

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


'*******************************************************************************
'* Public methods
'*******************************************************************************

'Processes 24 bpp bitmaps only. Convenience method as wrapper for the calls
'needed for loading the bitmap and counting the colors

'On failure returns -1, otherwise the count of unique colors of the bitmap

Public Function gUniqueColorCountFromFile(ByVal File As String) As Long
Dim DIB As Long
DIB = gLoadBitmapFromFile(File)
If DIB <> 0 Then
gUniqueColorCountFromFile = gUniqueColorCountFromDIB(DIB)
gFreeDIB DIB
Else
gUniqueColorCountFromFile = -1
End If
End Function

'Loads 24 bpp bitmaps only. Scanlines may be padded or not.

'On failure returns 0, otherwise a valid DIB, which has to be freed later!

Public Function gLoadBitmapFromFile(ByVal File As String) As Long
Dim BmpBitsPerPixel As Long, DIB As Long

DIB = LoadImage(0&, File, IMAGE_BITMAP, 0, 0, _
LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
If DIB = 0 Then Exit Function
GetBitmapInfo DIB, BmpBitsPerPixel
If BmpBitsPerPixel <> 24 Then gFreeDIB DIB: Exit Function

gLoadBitmapFromFile = DIB
End Function

Public Sub gFreeDIB(ByRef DIB As Long)
If DIB <> 0 Then DeleteObject DIB: DIB = 0
End Sub

'Processes 24 bpp bitmaps only, DIB from function gLoadBitmapFromFile
'During processing creates and uses a 2097152 bytes big array!

'Is runnable in the IDE or compiled to pcode, but for better performance
'compile to native code. Max performance is achieved when array bounds
'and integer overflow checks are disabled.

'On failure returns -1, otherwise the count of unique colors of the bitmap

Public Function gUniqueColorCountFromDIB(ByVal DIB As Long) As Long
Dim BmpWidth As Long, BmpHeight As Long, BmpBitsPerPixel As Long
Dim BmpScanLineSize As Long, BmpBitsAddress As Long
Dim ScanLine() As Long, ScanLineSA As SAFEARRAY1D
Dim Count As Long
Dim ExcessPixels As Long, MaxIdx As Long
Dim Flags() As Long, FlagIdx As Long
Dim Bitmask As Long, Bitmasks() As Long
Dim i As Long, RunIdx As Long, ScanLineIdx As Long

On Error GoTo MethodError

'* Setup all

'524288 Longs, room for 16777216 = 2^24 bits
ReDim Flags(0 To 2 ^ 19 - 1) As Long

ReDim Bitmasks(0 To 31)
Bitmasks(0) = 1
For i = 1 To 30
Bitmasks(i) = Bitmasks(i - 1) * 2&
Next i
Bitmasks(i) = &H80000000

GetBitmapInfo DIB, BmpBitsPerPixel, BmpWidth, BmpHeight, _
BmpScanLineSize, BmpBitsAddress
If BmpBitsPerPixel <> 24 Then GoTo MethodError

ScanLinePtrSet ScanLine, ScanLineSA, BmpWidth, BmpBitsAddress
ExcessPixels = BmpWidth Mod 4
MaxIdx = (BmpWidth - 1) \ 4

'* Now count

For ScanLineIdx = 0 To BmpHeight - 1
ScanLineSA.pvData = UAdd(BmpBitsAddress, ScanLineIdx * BmpScanLineSize)
For RunIdx = 1 To 4
i = MaxIdx
If ExcessPixels <> 0 Then
If RunIdx > ExcessPixels Then i = i - 1
End If
For i = 0 To i * 3 Step 3 'Forwards 12 bytes per iteration
FlagIdx = ScanLine(i) And &HFFFFFF
Bitmask = Bitmasks(FlagIdx And &H1F&)
FlagIdx = FlagIdx \ 32&
If (Flags(FlagIdx) And Bitmask) = 0& Then
Count = Count + 1&
Flags(FlagIdx) = Flags(FlagIdx) Or Bitmask
End If
Next i
ScanLineSA.pvData = UAdd(ScanLineSA.pvData, 3)
Next RunIdx
Next ScanLineIdx

'* Finished

gUniqueColorCountFromDIB = Count
GoTo MethodExit

MethodError:
gUniqueColorCountFromDIB = -1
MethodExit:
ScanLinePtrFree ScanLine
End Function


'*******************************************************************************
'* Private helpers
'*******************************************************************************

Private Sub GetBitmapInfo(ByVal DIB As Long, _
Optional ByRef BmpBitsPerPixel As Long, _
Optional ByRef BmpWidth As Long, _
Optional ByRef BmpHeight As Long, _
Optional ByRef BmpScanLineSize As Long, _
Optional ByRef BmpBitsAddress As Long)
Dim BmpInfo As BITMAP

GetObject DIB, Len(BmpInfo), BmpInfo
With BmpInfo
BmpBitsPerPixel = .bmBitsPixel
BmpWidth = .bmWidth
BmpHeight = .bmHeight
BmpScanLineSize = .bmWidthBytes
BmpBitsAddress = .bmBits
End With
End Sub

Private Sub ScanLinePtrSet(ByRef ScanLine() As Long, _
ByRef ScanLineSA As SAFEARRAY1D, _
ByRef BmpWidth As Long, _
ByRef BmpBitsAddress As Long)
With ScanLineSA
.cDims = 1
.cbElements = 4
.cElements = BmpWidth
.pvData = BmpBitsAddress
End With
PokeLong VarPtrArray(ScanLine), VarPtr(ScanLineSA)
End Sub

Private Sub ScanLinePtrFree(ByRef ScanLine() As Long)
PokeLong VarPtrArray(ScanLine), 0
End Sub

'Unsigned addition
'Allowed ranges:
'Op1: 0 to &HFFFFFFFF
'Op2: 0 to &HFFFFFFFF

Private Function UAdd(ByVal Op1 As Long, ByVal Op2 As Long) As Long
UAdd = (((Op1 Xor &H80000000) + (Op2 And &H7FFFFFFF)) _
Xor &H80000000) Or (Op2 And &H80000000)
End Function


--
Ulrich Korndoerfer

VB tips, helpers, solutions -> http://www.prosource.de/Downloads/
MS Newsgruppen Alternativen -> http://www.prosource.de/ms-ng-umzug.html