Create BMP image with VBA - vba

I tried to create a macro which converts value of cells into BMP file.
Code is based on existing topic, found here:
VBA manually create BMP
Type typHEADER
strType As String * 2 ' Signature of file = "BM"
lngSize As Long ' File size
intRes1 As Integer ' reserved = 0
intRes2 As Integer ' reserved = 0
lngOffset As Long ' offset to the bitmap data (bits)
End Type
Type typINFOHEADER
lngSize As Long ' Size
lngWidth As Long ' Height
lngHeight As Long ' Length
intPlanes As Integer ' Number of image planes in file
intBits As Integer ' Number of bits per pixel
lngCompression As Long ' Compression type (set to zero)
lngImageSize As Long ' Image size (bytes, set to zero)
lngxResolution As Long ' Device resolution (set to zero)
lngyResolution As Long ' Device resolution (set to zero)
lngColorCount As Long ' Number of colors (set to zero for 24 bits)
lngImportantColors As Long ' "Important" colors (set to zero)
End Type
Type typPIXEL
bytB As Byte ' Blue
bytG As Byte ' Green
bytR As Byte ' Red
End Type
Type typBITMAPFILE
bmfh As typHEADER
bmfi As typINFOHEADER
bmbits() As Byte
End Type
Sub testowy()
Dim bmpFile As typBITMAPFILE
Dim lngRowSize As Long
Dim lngPixelArraySize As Long
Dim lngFileSize As Long
Dim j, k, l, x As Integer
Dim bytRed, bytGreen, bytBlue As Integer
Dim lngRGBColoer() As Long
Dim strBMP As String
With bmpFile
With .bmfh
.strType = "BM"
.lngSize = 0
.intRes1 = 0
.intRes2 = 0
.lngOffset = 54
End With
With .bmfi
.lngSize = 40
.lngWidth = 21
.lngHeight = 21
.intPlanes = 1
.intBits = 24
.lngCompression = 0
.lngImageSize = 0
.lngxResolution = 0
.lngyResolution = 0
.lngColorCount = 0
.lngImportantColors = 0
End With
lngRowSize = Round(.bmfi.intBits * .bmfi.lngWidth / 32) * 4
lngPixelArraySize = lngRowSize * .bmfi.lngHeight
ReDim .bmbits(lngPixelArraySize)
ReDim lngRGBColor(21, 21)
For j = 1 To 21 ' For each row, starting at the bottom and working up...
'each column starting at the left
For x = 1 To 21
If Cells(j, x).Value = 1 Then
k = k + 1
.bmbits(k) = 0
k = k + 1
.bmbits(k) = 0
k = k + 1
.bmbits(k) = 0
Else
k = k + 1
.bmbits(k) = 255
k = k + 1
.bmbits(k) = 255
k = k + 1
.bmbits(k) = 255
End If
Next x
Next j
.bmfh.lngSize = 14 + 40 + lngPixelArraySize
End With ' Defining bmpFile
strBMP = "C:\Lab\xxx.BMP"
Open strBMP For Binary Access Write As 1 Len = 1
Put 1, 1, bmpFile.bmfh
Put 1, , bmpFile.bmfi
Put 1, , bmpFile.bmbits
Close
End Sub
The output differs significantly from my expectations (left - actual output, right - expected output).

There is a small error in code.
Colors at BMP file are saved as: [B,G,R] 1st pixel [B,G,R] 2nd pixel [0,0] padding (gap) for 4 byte alignment. To mirror the image the first loop should be reversed. The correct code (including loops) should be like:
k = -1
For j = 21 To 1 Step -1
' For each row, starting at the bottom and working up...
'each column starting at the left
For x = 1 To 21
If Cells(j, x).Value = 1 Then
k = k + 1
.bmbits(k) = 0
k = k + 1
.bmbits(k) = 0
k = k + 1
.bmbits(k) = 0
Else
k = k + 1
.bmbits(k) = 255
k = k + 1
.bmbits(k) = 255
k = k + 1
.bmbits(k) = 255
End If
Next x
If (21 * .bmfi.intBits / 8 < lngRowSize) Then ' Add padding if required
For l = 21 * .bmfi.intBits / 8 + 1 To lngRowSize
k = k + 1
.bmbits(k) = 0
Next l
End If
Next j

Related

Change red pixels to blue in a bitmap

I want to change the red pixels to blue. The image is 24 bits .bmp. I am using lockbits because it is faster but the code doesnt find the red pixels!
Code:
Dim bmp As Bitmap = New Bitmap("path")
Dim pos As Integer
Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits _
(rect, Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat)
Dim ptr As IntPtr = bmpData.Scan0
Dim bytes As Integer = Math.Abs(bmpData.Stride) * bmp.Height
Dim rgbValues(bytes - 1) As Byte
Marshal.Copy(ptr, rgbValues, 0, bytes)
For y = 0 To bmp.Height - 1
For x = 0 To bmp.Width - 1
pos = y * bmp.Width * 3 + x * 3
If rgbValues(pos) = 255 And rgbValues(pos + 1) = 0 And rgbValues(pos + 2) = 0 Then
rgbValues(pos + 2) = 255
rgbValues(pos) = 0
End If
Next
Next
Marshal.Copy(rgbValues, 0, ptr, bytes)
bmp.UnlockBits(bmpData)
bmp.Save("new path")
Thank you!
The values that are stored in rgbValues are not in this order
R G B R G B.....
but
B G R B G R.....
so the correct code in your loop is:
' B G R
If rgbValues(pos) = 0 And rgbValues(pos + 1) = 0 And rgbValues(pos + 2) = 255 Then
rgbValues(pos + 2) = 0 'R
rgbValues(pos) = 255 'B
End If

Find the indices for the minimum values in a multi dimensional array in VBA

In the code below I have an n x n x n array of values. I need to identify the indices that contain the minimum, second to minimum, third to minimum, ..., and put them into their own array to be used later on in the code. CC is currently defined as a 11 x 11 x 11 array and I need to identify the minimums. Below is the setup of my array CC that contains the values. n is defined as the length of the array h2s, which happens to be 11 in this case. h2st is the sum of the values in h2s.
h2s = [1.099, 0.988, 0.7, 0.8, 0.5, 0.432, 0.8, 1.12, 0.93, 0.77, 0.658]
h2st = 0
n = Ubound(h2s) - Lbound(h2s) + 1
For i = 1 to n
h2st = h2st + h2s(i)
Next i
For i = 1 To n
For j = i + 1 To n
For k = j + 1 To n
CC(i, j, k) = Abs(h2st - ((h2s(i) + h2s(j) + h2s(k)) * (n / 3)))
Next k
Next j
Next i
You can use this function that takes a multidimensional array and returns an array of its n minimum values, where n is a parameter. Importantly, the elements in the returned array are a data structure of Type Point, containing the coordinates and the value of each found point.
You can easily adjust it for finding the n max values, just by changing two characters in the code, as indicated in comments (the initialization and the comparison)
Option Explicit
Type Point
X As Long
Y As Long
Z As Long
value As Double
End Type
Function minVals(ar() As Double, nVals As Long) As Point()
Dim i As Long, j As Long, k As Long, m As Long, n As Long, pt As Point
'Initialize returned array with max values.
pt.value = 9999999# ' <-------- change to -9999999# for finding max
ReDim ret(1 To nVals) As Point
For i = LBound(ret) To UBound(ret): ret(i) = pt: Next
For i = LBound(ar, 1) To UBound(ar, 1)
For j = LBound(ar, 2) To UBound(ar, 2)
For k = LBound(ar, 3) To UBound(ar, 3)
' Find first element greater than this value in the return array
For m = LBound(ret) To UBound(ret)
If ar(i, j, k) < ret(m).value Then ' <------- change to > for finding max
' shift the elements on this position and insert the current value
For n = UBound(ret) To m + 1 Step -1: ret(n) = ret(n - 1): Next n
pt.X = i: pt.Y = j: pt.Z = k: pt.value = ar(i, j, k)
ret(m) = pt
Exit For
End If
Next m
Next k
Next j
Next i
minVals = ret
End Function
Sub Test()
Dim i As Long, j As Long, k As Long, pt As Point
Const n As Long = 11
ReDim CC(1 To n, 1 To n, 1 To n) As Double
For i = 1 To n
For j = 1 To n
For k = 1 To n
CC(i, j, k) = Application.RandBetween(100, 100000)
Next k
Next j
Next i
' Testing the function: get the smalles 5 values and their coordinates
Dim mins() As Point: mins = minVals(CC, 5)
' Printing the results
For i = LBound(mins) To UBound(mins)
Debug.Print mins(i).value, mins(i).X, mins(i).Y, mins(i).Z
Next
End Sub

Diamond Square Algorithm in VBA (to run in excel)

I've written a script in VBA to create random terrain generation in excel, based on this following matlab script (http://knight.temple.edu/~lakamper/courses/cis350_2004/sources/matlabFractal/createFractalTerrain.m)
After compiling my script I found no bugs, but upon running in excel only cell A1 is assigned a value of zero, and then the script ends.
Now, I wondered if anyone had the time to look through my VBA script and see if they have any idea what's going wrong. I think maybe perhaps I mess around with an array called TR quite a bit when I could perhaps refer to the Cells directly from the get go.
Now, the code is bit long so I have provided a link to the text file that here, and so if nobody has the time I completely understand
https://www.dropbox.com/sh/c2l2ha0awirlowb/AAARGVpidQGP7I9Yu0XRN8yaa?dl=0
Also, here is the code indented.
Public TR(1 To 129, 1 To 129) As Double
Sub DiamondSquare()
Dim tsize As Long: tsize = 129
Dim StartRangRange As Double: startRandRange = 64.5
Dim H As Double: H = 0.9
Call createFractalTerrain(tsize, startRandRange, H)
End Sub
Function createFractalTerrain(ByVal tsize As Long, ByVal startRandRange As Double, ByVal H As Double) As Variant
'Function creates fractal terrain by midpoint displacement (diamond square algorithm)
'Output should be a tsize by tsize matrix
'tSize must be a (power of 2) + 1 ie 129
'startRandRange defines the overall elevation; size/2 gives natural images
'Roughness H (between 0 and 1); 0.9 is a natural value
'H=0 is max roughness
'Initiate Terrain
Dim i As Long
Dim j As Long
Dim ii As Long
Dim jj As Long
For i = 1 To tsize
For j = 1 To tsize
TR(i, j) = 10000
Next
Next
TR(1, 1) = 0
TR(1, tsize) = 0
TR(tsize, 1) = 0
TR(tsize, tsize) = 0
tsize = tsize - 1
randRange = startRandRange
'Main Loop
While tsize > 1
Call diamondStep(tsize, randRange)
Call squareStep(tsize, randRange)
tsize = tsize / 2
randRange = randRange * (1 / (2 ^ H))
Wend
For ii = 1 To tsize
For jj = 1 To tsize
Cells(ii, jj).Value = TR(ii, jj)
Next
Next
End Function
Sub diamondStep(ByVal tsize As Integer, ByVal randRange As Double)
sh = tsize / 2
maxIndex = UBound(TR, 1)
RowVal = 1 + sh
ColVal = 1 + sh
While RowVal < maxIndex
While ColVal < maxIndex
'Average height value of 4 cornerpoints
ValueH = TR(RowVal - sh, ColVal - sh) + TR(RowVal - sh, ColVal + sh) + TR(RowVal + sh, ColVal - sh) + TR(RowVal + sh, ColVal + sh)
ValueH = ValueH / 4
'Displacement
displacement = Int((1 - 0 + 1) * Rnd + 0) * randRange - randRange / 2
ValueH = ValueH + displacement
'Set diamond point
If TR(RowVal, ColVal) = 10000 Then TR(RowVal, ColVal) = ValueH
'Next square in same row
ColVal = ColVal + tsize
Wend
'Next row
ColVal = 1 + sh
RowVal = RowVal + tsize
Wend
End Sub
Sub squareStep(ByVal tsize As Integer, ByVal randRange As Double)
sh = tsize / 2
maxIndex = UBound(TR, 1)
colStart = 1 + sh
RowVal = 1
ColVal = colStart
While (RowVal <= maxIndex)
While (ColVal <= maxIndex)
ValueH = 0
nop = 4 'number of points
'the following cases handle the boundary points,
'i.e. the incomplete diamonds
'north
If RowVal > 1 Then
ValueH = ValueH + TR(RowVal - sh, ColVal)
Else
nop = nop - 1
End If
'east
If ColVal < maxIndex Then
ValueH = ValueH + TR(RowVal, ColVal + sh)
Else
nop = nop - 1
End If
'south
If RowVal < maxIndex Then
ValueH = ValueH + TR(RowVal + sh, ColVal)
Else
nop = nop - 1
End If
'west
If ColVal > 1 Then
ValueH = ValueH + TR(RowVal, ColVal - sh)
Else
nop = nop - 1
End If
'displacement
displacement = Int((1 - 0 + 1) * Rnd + 0) * randRange - randRange / 2
ValueH = ValueH / nop + displacement
'set square point (if not predefined)
If TR(RowVal, ColVal) = 10000 Then TR(RowVal, ColVal) = ValueH
'next diamond in same row
ColVal = ColVal + sh
Wend
'next row
'the starting column alternates between 1 and sh
If colStart = 1 Then
colStart = sh + 1
Else
colStart = 1
End If
ColVal = colStart
RowVal = RowVal + sh
Wend
End Sub
I think the issue you are experiencing is from not iterating over the array you created as you are resetting the tsize variable to 1.
Changing your code to something like this:
For ii = 1 To 129
For jj = 1 To 129
Cells(ii, jj).Value = TR(ii, jj)
Next
Next
Produces a grid of 129 Rows and 129 columns with numeric values. Alternatively you could use the LBound(TR) and UBound(TR) to achieve the same result as manually typing 1 to 129 in each of the For...Loop. I played around with this, and used a conditional format to color the cells based on their relative size either black or white. Here is the result, I think this is the type of output you are expecting.

vba array element removal

j = LBound(arrayTime)
Do Until j = UBound(arrayTime)
j = j + 1
b = b + 1
cnc = b + r
MsgBox cnc
If cnc > 7 Then
b = 0
r = 0
cnc = b + r
End If
numMins = Sheet5.Cells(cnc + 3, 2) - arrayTime(j)
If numMins < 0 Then
g = g + 1
ReArrangeArray arrayTime, j
'ReDim Preserve arrayTime(numrows - 1 + g)
'arrayTime(numrows - 1 + g) = arrayTime(j)
'MsgBox (arrayTime(numrows - 1 + g))
Else
Sheet5.Cells(cnc + 3, 2) = numMins
End If
Loop
If the if statement is true I want to be able to put the array value at the end of the array and remove that value from its current spot. As the code is, it just adds it to the end and increases the size of the array from 12 to 13. How can I get the array to remain size 12 and still place the value at the end of the array and then remove it from its original position? I do not want to touch the array values in front. Just want to take that value and move it to the end.
For instance
array(1,2,3,4,5)
If statement
j on third loop.
array(j)=3
end array should be
array(1,2,4,5,3)
You could use a helper Sub like this one:
Sub ReArrangeArray(inputArray as Variant, indexToSwap as long)
Dim I As Long
Dim tempVal As Variant
If indexToSwap >= LBound(inputArray) And indexToSwap < UBound(inputArray) Then
tempVal = inputArray(indexToSwap)
For I = indexToSwap To UBound(inputArray) - 1
inputArray(i) = inputArray(i + 1)
Next I
InputArray(UBound(inputArray)) = tempVal
End If
End Sub
To be called by your main Sub as follows:
ReArrangeArray arrayTime, j

Simulation runs fine # 10k cycles, but gets error 13 (type mismatch) # 100k cycles

First off, here's my code:
Sub SimulatePortfolio()
Dim lambda As Double
Dim num As Integer
Dim cycles As Long
Column = 12
q = 1.5
lambda = 0.05
cycles = 100000
Dim data(1 To 100000, 1 To 10) As Integer
Dim values(1 To 10) As Double
For i = 1 To 10
values(i) = 0
Next i
temp = lambda
For i = 1 To cycles
lambda = temp
num = 10
t = 0
Dim temps(1 To 10) As Integer
For k = 1 To 10
temps(k) = 1000
Next k
Do While (t < 10 And num > 0)
t = t + tsim(lambda, num)
For j = 1 To 10
If (j > t) Then
temps(j) = temps(j) - 50
End If
Next j
num = num - 1
If (num <= 0) Then
Exit Do
End If
lambda = lambda * q
Loop
For l = 1 To 10
values(l) = values(l) + temps(l)
data(i, l) = temps(l)
Next l
Next i
For i = 1 To 10
Cells(i + 1, Column) = values(i) / cycles
'Problem occurs on this line:
Cells(i + 1, Column + 1).Value = Application.WorksheetFunction.Var(Application.WorksheetFunction.Index(data, i, 0))
Next i
End Sub
Function tsim(lambda As Double, num As Integer) As Double
Dim v As Double
Dim min As Double
Randomize
min = (-1 / lambda) * Log(Rnd)
For i = 1 To (num - 1)
Randomize
v = (-1 / lambda) * Log(Rnd)
If (min > v) Then
min = v
End If
Next i
tsim = min
End Function
When I set the value for cycles to 10000, it runs fine without a hitch. When I go to 100000 cycles, it gets an Error 13 at the indicated line of code.
Having been aware that Application.Tranpose is limited to 65536 rows with variants (throwing the same error) I tested the same issue with Index
It appears that Application.WorksheetFunction.Index also has a limit of 65536 rows when working with variants - but standard ranges are fine
So you will need to either need to dump data to a range and work on the range with Index, or work with two arrays
Sub Test()
Dim Y
Dim Z
'works in xl07/10
Debug.Print Application.WorksheetFunction.Index(Range("A1:A100000"), 1, 1)
Y = Range("A1:A65536")
`works
Debug.Print Application.WorksheetFunction.Index(Y, 1, 1)
'fails in xl07/10
Z = Range("A1:A65537")
Debug.Print Application.WorksheetFunction.Index(Z, 1, 1)
End Sub