Color values in Excel VBA - excel

I've done a bit of searching for this one and cannot find the answer (or maybe looking in the wrong places :/)
I am trying to find a reference for the colour values used in excel VBA. I have a few simple lines of code which change the background colour in a selected cell (in this case I am using a simple macro to invoke the code).
I have a come across this value which was generated by the macro recorder:
Selection.Interior.Color = 15773696
My preference is to use RGB (the RGB equivalent of the above example is 128, 128, 128) and I have been trying find out where the macro recorder got this value from.
Apologies if this is a FAQ.
TIA

To convert the output in the terms of RGB:
Option Explicit
Public Sub ConvertColor()
Convert2RGB 15773696
End Sub
Public Sub Convert2RGB(ByVal clr As Long)
Dim R As Long, G As Long, B As Long
R = clr Mod 256
G = clr \ 256 Mod 256
B = clr \ 65536 Mod 256
'Or
'R = clr And 255
'G = clr \ 256 And 255
'B = clr \ 256 ^ 2 And 255
Debug.Print "R:" & R & " G:" & G & " B:" & B '15773696 -> R:0 G:176 B:240
Debug.Print RGB(R, G, B) 'R:0 G:176 B:240 -> 15773696
End Sub
From SO: Return RGB values from Range.Interior.Color (or any other Color Property)
...The number is a mathematical combination of the RGB values (B256^2 +
G256 + R) and a conversion of the hex color value to a decimal number
(base 16 to base 10)...
...a decimal conversion of the typical hexidecimal numbers that we are
used to seeing for colors in html e.g. "66FF66"...
More details (external link): Determining the RGB Value of a Color

Related

Convert Excel column width between characters unit and pixels (points)

"One unit of column width is equal to the width of one character in the Normal style. For proportional fonts, the width of the character 0 (zero) is used."
So ColumnWidth in Excel is measured as a number of "0" characters which fits in a column. How can this value be converted into pixels and vice versa?
As already mentioned ColumnWidth value in Excel depends on default font of a Workbook which can be obtained via Workbook.Styles("Normal").Font. Also it depends on current screen DPI.
After carrying out some research for different fonts and sizes in Excel 2013 I've found out that we have 2 linear functions (Arial cannot be seen because it overlaps with Tahoma.):
As it can be seen in the picture the function for ColumnWidth < 1 is different from the major part of the line chart. It's calculated as a number of pixels in a column / number of pixels needed to fit one "0" character in a column.
Now let's see what a typical cell width consists of.
A - "0" character width in the Normal Style
B - left and right padding
C - 1px right margin
A can be calculated with GetTextExtentPoint32 Windows API function, but font size should be a little bit bigger. By experiment I chose +0.3pt which worked for me for different fonts with 8-48pt base size. B is (A + 1) / 4 rounded to integer using "round half up". Also screen DPI will be needed here (see Python 3 implementation below)
Here are equations for character-pixel conversion and their implementation in Python 3:
import win32print, win32gui
from math import floor
def get_screen_dpi():
dc = win32gui.GetDC(0)
LOGPIXELSX, LOGPIXELSY = 88, 90
dpi = [win32print.GetDeviceCaps(dc, i) for i in (LOGPIXELSX,
LOGPIXELSY)]
win32gui.ReleaseDC(0, dc)
return dpi
def get_text_metrics(fontname, fontsize):
"Measures '0' char size for the specified font name and size in pt"
dc = win32gui.GetDC(0)
font = win32gui.LOGFONT()
font.lfFaceName = fontname
font.lfHeight = -fontsize * dpi[1] / 72
hfont = win32gui.CreateFontIndirect(font)
win32gui.SelectObject(dc, hfont)
metrics = win32gui.GetTextExtentPoint32(dc, "0")
win32gui.ReleaseDC(0, dc)
return metrics
def ch_px(v, unit="ch"):
"""
Convert between Excel character width and pixel width.
`unit` - unit to convert from: 'ch' (default) or 'px'
"""
rd = lambda x: floor(x + 0.5) # round half up
# pad = left cell padding + right cell padding + cell border(1)
pad = rd((z + 1) / 4) * 2 + 1
z_p = z + pad # space (px) for "0" character with padding
if unit == "ch":
return v * z_p if v < 1 else v * z + pad
else:
return v / z_p if v < z_p else (v - pad) / z
font = "Calibri", 11
dpi = get_screen_dpi()
z = get_text_metrics(font[0], font[1] + 0.3)[0] # "0" char width in px
px = ch_px(30, "ch")
ch = ch_px(px, "px")
print("Characters:", ch, "Pixels:", px, "for", font)
2022 and still the same Problem... Found threads going back to 2010 having the issue...
To start of: Pixel != Points
Points are defined as 72points/inch: https://learn.microsoft.com/en-us/office/vba/language/glossary/vbe-glossary#point
Though that definition seems stupid, as a shape with a fixed width of 100points, would display the exact same size in inch on every monitor independent of monitor configuration, which is not the case.
Characters is a unit that is defined to the number of 0 characters of the default text format. A cell set to a width of 10 characters, can fit 10 "0" characters, when the cell content is formatted to the default format.
My case is that I need to place pictures into the document and place text into cells next to it. But pictures hover over the document and cells are hidden below it. Depending on the size of the Picture, more or less cells are hidden. Thus, I can't just say I place text 5 cells to the left of the picture. Autosizing a column to the contents of the cells of the column, does not account for the hovering picture.
A picture is bound to the cell that is below the top left corner of the picture. I need to set the size of that cell to the size of the picture to solve the issue.
A Picture is a Shape. A Shape returns its width as Points (Shape.Width).
A Range can be set to a cell like Worksheet.Range["A1"]. From a Range you can get the width in Characters (Range.ColumnWidth) or in Points (Range.Width). But you can only set the width of a Range in Characters (Range.ColumnWidth).
So we can retrieve the size of the Picture (Shape) in Points and need to convert them to Characters to set the cell to the correct width...
Some research showed that the Points size of a cell contains a constant for spacing (padding before and after the cell content) and probably the seperator lines between cells.
On my system:
A cell set to a width of 1 **Characters** = 9 **Points**
A cell set to a width of 2 **Characters** = 14.25 **Points**
A cell set to a width of 3 **Characters** = 19.5 **Points**
As I said, there is a constant within the Points. Thus going from 1 Characters, to 2 Characters, the difference is only the size of the letter.
SizeOfLetter = 14.25 Points - 9 Points = 5.25 Points
we can then subtract that SizeOfLetter from the Points for 1 Characters and get the Points constant.
PointsConstant = 9 Points - 5.25 Points = 3.75 Points
Verify:
Points size for a cell containing 3 "0" letters = 3SizeOfLetter + PointsConstant = 35.25 Points + 3.75 Points = 19.5 Points
As the values depend on your system, YOU CAN'T USE THOSE VALUES!
Best way is to use code to calculate it for your system:
C# code:
Excel.Application excelApp = new Excel.Application();
Excel.Workbook workbook1 = excelApp.Workbooks.Add();
Excel.Worksheet sheet1 = (Excel.Worksheet)workbook1.ActiveSheet;
// Evaluate the Points data for the document
double previousColumnWidth = (double)sheet1.Range["A1"].ColumnWidth;
sheet1.Range["A1"].ColumnWidth = 1; // Make the cell fit 1 character
double points1 = (double)sheet1.Range["A1"].Width;
sheet1.Range["A1"].ColumnWidth = 2; // Make the cell fit 2 characters
double points2 = (double)sheet1.Range["A1"].Width;
double SizeOfLetter = points2 - points1;
double PointsConstant = points1 - pointsPerCharater;
// Reset the column width
sheet1.Range["A1"].ColumnWidth = previousColumnWidth;
// Create a function for the conversion
Func<double, double> PointsToCharacters = (double points) => (points - PointsConstant ) / SizeOfLetter ;

How to fix "Sub or Function not defined" when using HSL Function

I'm trying to set the interior color of some cells using HSL instead of RGB.
Judging by this documentation from Microsoft, it's very simple:
https://learn.microsoft.com/en-us/office/client-developer/visio/hsl-function
The syntax is simply HSL(** hue **, ** saturation **, ** luminosity ** )
Why, then, is my VBA telling me the sub or function is not defined?
The same error occurs for both of these lines of code:
Range("A1").Interior.Color = HSL(160, 240, 120)
Range("A1").Interior.ColorIndex = HSL(160, 240, 120)
Here's an alternative method for converting between RGB → HSL in VBA, using a Windows API. Since it's a "Classic Windows" function it has a couple idiosyncrasies.
RGB to HSL with ColorRGBToHLS
ColorRGBToHLS is an Windows API function which therefore uses an outdated color range that's been carried forward since the 1980's (originally used in a long-defunct program called MS Chart). You may be familiar with converting R/G/B values between percentages and values out of 255.
However in this case the conversion must be to/from values out of 240, not 255.
Just to further confuse us here in the future, they used notation of HLS instead of more-common HSL (and hex color strings are 0xBBGGRR instead of #RRGGBB).
tl;dr
Option Explicit
Public Declare PtrSafe Sub ColorRGBToHLS Lib "shlwapi.dll" (ByVal clrRGB As Long, _
wHue As Integer, wLuminance As Integer, wSaturation As Integer)
Function rgb_to_hsl(r As Integer, g As Integer, b As Integer)
Dim h As Integer, s As Integer, l As Integer
ColorRGBToHLS RGB(r, g, b), h, l, s
h = 360 * (h / 239)
s = 100 * (s / 240)
l = 100 * (l / 240)
rgb_to_hsl = "hsl(" & h & "," & s & "%," & l & "%)"
End Function
...and a demo:
Sub test()
Debug.Print rgb_to_hsl(255, 180, 63) 'returns "hsl(36,100%,62%)"
End Sub
By the way the API also has a ColorHLSToRGB function.
The 80's was a crazy (often confusing) time for computer nerds. Fun fact: Bill Gates married the first female programmer he hired.

Lua: color fading function

I'm trying to create a function that inputs two RGB colors and a percentage then returns a color in-between the two based off of the percentage.
I found the Dec2Hex function somewhere online and figured it would be useful.
Right now I have tried:
function Dec2Hex(nValue) -- http://www.indigorose.com/forums/threads/10192-Convert-Hexadecimal-to-Decimal
if type(nValue) == "string" then
nValue = String.ToNumber(nValue);
end
nHexVal = string.format("%X", nValue); -- %X returns uppercase hex, %x gives lowercase letters
sHexVal = nHexVal.."";
if nValue < 16 then
return "0"..tostring(sHexVal)
else
return sHexVal
end
end
function fade_RGB(colour1, colour2, percentage)
r1, g1, b1 = string.match(colour1, "#([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])")
r2, g2, b2 = string.match(colour2, "#([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])([0-9A-F][0-9A-F])")
r3 = (tonumber(r1, 16)/tonumber(r2, 16))*(percentage)
g3 = (tonumber(g1, 16)/tonumber(g2, 16))*(percentage)
b3 = (tonumber(b1, 16)/tonumber(b2, 16))*(percentage)
return "#"..Dec2Hex(r3).. Dec2Hex(g3)..Dec2Hex(b3)
end
I think I'm headed in the right direction but the math isn't right and I can't figure out how to fix it. Thanks in advance!
No Name's answer is almost right, but he's not merging the two colors based on the percentage.
What you instead want is to do a linear interpolation of the two values (though know that human vision/light wise this isn't how interpolating colors works, but a lot of libraries do it this way because it is easy and works for simple cases).
r3 = tonumber(r1, 16)*(100-percentage)/100.0 + tonumber(r2, 16)*(percentage)/100.0
As you may notice multiplying and dividing the percentages by 100 is kind of tedious, so you may want to pass it in already divided.
If I'm right, the line
r3 = (tonumber(r1, 16)/tonumber(r2, 16))*(percentage)
should be
r3 = math.abs(tonumber(r1, 16) - tonumber(r2, 16))*(percentage/100)
The other similar lines follow the same concept.
EDIT:
r3 = math.min(tonumber(r1, 16), tonumber(r2, 16)) +
math.abs(tonumber(r1, 16) - tonumber(r2, 16)) * (percentage/100)
should yield red for fade_RGB("#FF0000", #0000FF, 0) and blue for fade_RGB("#FF0000", #0000FF, 100).

How to change color of a cell in Excel file with Iron Python?

How do I change cell background color in Excel from Iron Python?
I tried following code:
def rgb_to_hex(rgb):
strValue = '%02x%02x%02x' % rgb
iValue = int(strValue, 16)
return iValue
worksheet.range["H6"].interior.color = rgb_to_hex((255,255,0))
But does not work. It is giving HResult error
I am not familiar with Iron Python, but in C# I do the following:
Range colorCells = WS.get_Range(WS.Cells[row.Row, locCol], WS.Cells[row.Row, locCol + locWid]);
colorCells.Interior.Color = System.Drawing.ColorTranslator.ToOle(color);
My guess is that Tim has the right suggestion, you need to use a different way to convert your RGB to a color that interior.color can accept.
In my C#, it is assigning the color as an int.
public static int ToOle(Color c);
I've managed to enter the color in two ways:
Using ColorIndex
https://learn.microsoft.com/en-us/office/vba/api/excel.colorindex
worksheet.Range["H6"].Interior.ColorIndex = 3
With System.Drawing.ColorTranslator.ToOle(color), like Owen Ivory suggested
clr.AddReference('System.Drawing')
from System.Drawing import Color, ColorTranslator
def rgbForExcel(r, g, b):
return ColorTranslator.ToOle(Color.FromArgb(r, g, b))
worksheet.Range["H6"].Interior.Color = rgbForExcel(255, 50, 0)

Programmatically darken a Hex colour [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
What's the easiest way to programmatically darken a hex colour?
If you're not bothered about too much control, and just want a generally darker version of a colour, then:
col = (col & 0xfefefe) >> 1;
Is a nice quick way to halve a colour value (assuming it's packed as a byte per channel, obviously).
In the same way brighter would be:
col = (col & 0x7f7f7f) << 1;
Convert hex color into integer RBG components:
#FF6600 = rbg(255, 102, 0)
If you want to make it darker by 5%, then simply reduce all integer values by 5%:
255 - 5% = 242
102 - 5% = 96
0 - 5% = 0
= rbg(242, 96, 0)
Convert back to hex color
= #F26000
A function implemented in javascript:
// credits: richard maloney 2006
function getTintedColor(color, v) {
if (color.length >6) { color= color.substring(1,color.length)}
var rgb = parseInt(color, 16);
var r = Math.abs(((rgb >> 16) & 0xFF)+v); if (r>255) r=r-(r-255);
var g = Math.abs(((rgb >> 8) & 0xFF)+v); if (g>255) g=g-(g-255);
var b = Math.abs((rgb & 0xFF)+v); if (b>255) b=b-(b-255);
r = Number(r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r).toString(16);
if (r.length == 1) r = '0' + r;
g = Number(g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g).toString(16);
if (g.length == 1) g = '0' + g;
b = Number(b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b).toString(16);
if (b.length == 1) b = '0' + b;
return "#" + r + g + b;
}
Example:
> getTintedColor("ABCEDEF", 10)
> #c6f7f9
Well, I don't have any pseudocode for you, but a tip. If you want to darken a color and maintain its hue, you should convert that hex to HSB (hue, saturation, brightness) rather than RGB. This way, you can adjust the brightness and it will still look like the same color without hue shifting. You can then convert that HSB back to hex.
given arg darken_factor # a number from 0 to 1, 0=no change, 1=black
for each byte in rgb_value
byte = byte * (1 - darken_factor)
I pieced together a nice two-liner function for this:
Programmatically Lighten or Darken a hex color (or rgb, and blend colors)
shadeColor2(hexcolor,-0.05) for 5% darker
shadeColor2(hexcolor,-0.25) for 25% darker
Use positives for lightening.
Split the hex color into its RGB components.
Convert each of these components into an integer value.
Multiply that integer by a fraction, such as 0.5, making sure the result is also integer.
Alternatively, subtract a set amount from that integer, being sure not to go below 0.
Convert the result back to hex.
Concatenate these values in RGB order, and use.
RGB colors (in hexadecimal RGB notation) get darker or lighter by adjusting shade, key, lightness, or brightness. See the playground: colorizer.org
Option 1. Translate R, G, B values to darken shade
This one is simple, but easy to mess up. Here is subtracting 16 points off the (0,255) scale from each value:
myHex = 0x8c36a9;
darkerHex = myHex - 0x101010;
# 0x7c2699;
The hex will underflow if any of the R,G,B values are 0x0f or lower. Something like this would fix that.
myHex = 0x87f609;
darkenBy = 0x10;
floor = 0x0;
darkerHex = (max((myHex >> 16) - darkenBy, floor) << 16) + \
(max(((myHex & 0xff00) >> 8) - darkenBy, floor) << 8) + \
max(((myHex & 0xff) - darkenBy), floor);
# 0x77e600
# substitute `ceiling=0xff;` and `min((myHex ...) + lightenBy, ceiling)` for lightening
Option 2. Scale R, G, B values to increase black
In the CMYK model, key (black) is 1 - max of R, G, B values on (0,1) scale.
This one is simple enough that you can get good results without too much code. You're rescaling the distribution of R, G, B values by a single scaling factor.
Express the scaling factor as 2-digit hex (so 50% would be .5*0x100 or 0x80, 1/16th is 0x10 and 10% rounds down to 0x19 ).
# Assumes integer division ... looking at you python3 >:(
myHex = 0x8c36a9;
keyFactor = 0x10; # Lighten or darken by 6.25%
R = myHex >> 16; # 0x8c
G = (myHex & 0xff00) >> 8; # 0x36
B = myHex & 0xff; # 0xa9
darkerHex = ((R-R*keyFactor/0x100) << 16) + # Darker R
((G-G*keyFactor/0x100) << 8) + # Darker G
(B-B*keyFactor/0x100); # Darker B
# 0x84339f
# substitute `(X+keyFactor-X*keyFactor/0x100)` for lightening
# 0x9443af
Option 3. Reduce Lightness or Brightness at constant hue
In the HSL representation of RGB, lightness is the midpoint between min and max of R, G, B values. For HSV, brightness is the max of R, G, B values.
Consider using your language's built-in or external RGB/HEX to HSL/HSV converter. Then adjust your L/V values and convert back to RGB/HSL. You can do the conversion by hand, as in #1 & #2, but the implementation may not save you any time over an existing converter (see links for the maths).
You should consider darken the color in L*a*b* color space. Here's an example in JavaScript using chroma.js:
chroma.hex("#FCFC00").darker(10).hex() // "#dde000"
A hex colour such as #FCFCFC consists of three pairs representing RGB. The second part of each pair can be reduced to darken any colour without altering the colour considerably.
eg. to darken #FCFCFC, lower the values of C to give #F0F0F0
Reducing the first part of each pair by a small amount will also darken the colour, but you will start to affect the colour more (eg. turning a green to a blue).

Resources