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

"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 ;

Related

openpyxl: how to fit a chart inside a single cell?

The problem is: column.width, row.height and chart.width, chart.height use different units. Also it seems it might depend on the os, application and display. Is it even possible to fit a chart inside a single cell so it gonna be rendered the same everywhere?
UPD: The code looks like
width = 26
height = 78
for c in range(ws.min_column, ws.max_column + 1):
ws.column_dimensions[get_column_letter(c)].width = width
for r in range(ws.min_row, 10):
ws.row_dimensions[r+1].height = height
chart.width = width / 5.425
chart.height = height / 29

Convert all the BGR value within a specific range to white(255,255,255)

I have input png image of which I want to convert all the pixels belonging to specific range starting (2,2,2) ending (255,255,255) to white(255,255,255)
im = cv2.imread('3.png') # I am reading the image
lower_range = np.array([2,2,2]). # I specific the lower range
upper_range = np.array([255,255,255]) # I specify the upper range
im[np.where((im == [0,0,255]).all(axis = 2))] = [255,255,255] # converts all red pixels to white
cv2.imwrite('out.png', im)
My question is how can I modify im[np.where((im == [0,0,255]).all(axis = 2))] = [255,255,255]. Such that it covers range of colours mentioned in line 2 and 3 and converts them all to white.
There's cv2.inRange which yields a mask which can be used to change color as you wish.
mask1 = cv2.inRange(im, lower_range, upper_range)
im[np.where(mask)] = [255,255,255]
On a side note, your range of colors is pretty big (almost covers everything).

Shape doesn't show all text

I need to create shapes labelled with a number but the shape doesn't display the whole number.
For example, where the number is 1 to 9 it displays, but for 10 to 19 it shows just 1.
I don't want to change the radius it have to be the same.
Sheets(xRiS).Select
Dim textRectangles As Shape
Dim radius As Integer
radius = 15
Set textRectangles = ActiveSheet.Shapes.AddShape(msoShapeOval, (Range("D13").Left - radius / 2), _
Range("D13").Top - radius / 2, radius, radius)
textRectangles.Name = "Groups " & i
textRectangles.TextFrame.Characters.Text = i
textRectangles.TextFrame.VerticalAlignment = xlVAlignCenter
textRectangles.TextFrame.HorizontalAlignment = xlHAlignCenter
textRectangles.TextFrame.Characters.Font.Size = 9
textRectangles.TextFrame.Characters.Font.Name = "Georgia"
textRectangles.Fill.ForeColor.RGB = RGB(220, 105, 0)
textRectangles.Fill.Transparency = 0.5
This is the current result:
This is the result I want:
How can I configure the shape to display the number where the number is 10-19?
There are a couple of theoretical options, but neither seem like they are going to work with radius = 15 and textRectangles.TextFrame.Characters.Font.Size = 9.
First is textRectangles.TextFrame.AutoSize = True which resizes the shape to fix the text - however, this is going to expand the radius significantly.
Second is textRectangles.TextFrame2.AutoSize = msoAutoSizeTextToFitShape - however, this only works if the minimum value is radius = 33.
You may have to consider increasing the radius.

Prevent to patch if the area is already filled

This is quite a tough challenge I have with my code. First of all the code I am putting here is not runnable because I am using an Excel sheet (but I am happy to email it if people want to try using my code).
What I have is an Excel sheet with data on cross-sectional fibres in a microscopic image I took. The information is basically: location of the section, area, angle of rotation.
From that I calculate the angle of orientation Phi, and Gamma. After that I use the scatter function to plot a dot of different colors for each Phi angle value. I use a constant color for a range of 10 degrees. Which gives me a picture like this:
Now my aim to is calculate the area of each homogeneous region. So I look for a way to plot let's say all the dots within the -10 +10 region (I'm doing 20 degrees for now, but will do 10 after). I used a look and I get a picture like this:
The white corresponds where the dots are within the range I selected. After that I use the toolbox in MATLAB to convert each dot into a pixel. So I'll get a black background with loads of white pixels, then I use imdilate to make circles, fill holes and isolate each region with a specific color. Finally I use the functions boundary and patch, to create each boundary and fill them with a color. And I get a picture like this:
Which is what I want and I can get the area of each region and the total area (I used a threshold to discard the small areas). Then I run the code several time for each region, and I use imfuse to put them back together and see what it looks like.
THE PROBLEM is, they overlap quite a lot, and that is because there are some errors in my data, and therefore some blue dots will be in the red and so on.
So I want to run the code once, then when I rerun it with another range, it does the same thing but doesn't take into account value when there's already something plotted before.
I tried to do that by, after running once, saving the matrix bw4 and adding a condition when plotting the black and white pic, by saying if Phi is in my range AND there no white here then you can put white, otherwise it's black. But it doesn't seem to work.
I understand this is quite a complicated thing to explain, but I would appreciate any ideas, and open to chat via email or otherwise. I am putting the full code now, and I can send you my Excel sheet if you want to run it on your computer and see for yourself.
clearvars -except data colheaders bw4
close all
clc
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% CHANGE DATA FOR EACH SAMPLE %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cd 'C:\Users\dkarta\Desktop\Sample 12\12.6'
data=xlsread('Sample12_6res.xlsx');
cd 'C:\Users\dkarta\Documents\MATLAB'
%data=Sample121res; % Data name
imax=length(data); % Numbers of rows in data sheet
y=11900; % Number of pixels in the y on image j
%%
data(:,15)=data(:,9)*pi/180; % Convers Column 9 (angle of rotation) in rads
data(:,16)=y-data(:,6); % Reset the Y coordinate axis to bottom left
delta = 0 : 0.01 : 2*pi; % Angle in paramteric equations
theta=45*pi/180; % Sample cutting angle in rads
%AA=[data(:,5)' data(:,16)' phi']
% Define colors
beta=acos(data(1:imax,8)./data(1:imax,7));%./acos(0);
phi=atan(sin(beta).*cos(data(1:imax,15))./(sin(theta)*sin(beta).*sin(data(1:imax,15))+cos(theta)*cos(beta)))/(pi/2);
phi2=phi/2+1/2; % Scales in plane angle phi between 0 and 1
gamma=atan((cos(theta)*sin(beta).*sin(data(1:imax,15))-sin(theta)*cos(beta))./...
(sin(theta)*sin(beta).*sin(data(1:imax,15))+cos(theta)*cos(beta)))/(pi/2);
gamma2=gamma+1/2; % Scales out of plane angle gamma between 0 and 1
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% MESHGRID AND COLOURMAP %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%
x1=data(1:imax,5);
y1=data(1:imax,16);
z1=phi*90;
z2=gamma*90;
n=300;
%Create regular grid across data space
[X,Y] = meshgrid(linspace(min(x1),max(x1),n), linspace(min(y1),max(y1),n));
% Creating a colormap with 10 degree constant colors
map4=[0 0 1;0 1/3 1;0 2/3 1; 0 1 1;0 1 2/3;0 1 1/3;0 1 0;1/3 1 0;2/3 1 0;1 1 0;1 0.75 0;1 0.5 0;1 0.25 0;1 0 0;0.75 0 0.25;0.5 0 0.5;0.25 0 0.75; 0 0 1];
Colormap4=colormap(map4);
h=colorbar;
caxis([-90 90])
set(h, 'YTick', [-90:10:90])
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% PLOT USING SCATTER - ISOLATE SOME REGIONS %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
a=-10; % Lower boundary for angle interval
b=10; % Upper boundary for angle interval
c=z1>a & z1 < b;
c=c.*1;
%j=1;
y1=(y1-min(y1)+1);
y2=max(y1)-y1+1;
[X1,Y1]=meshgrid(1:500,1:500);
griddata(x1,y2,c,X1,Y1);
clear c1
for i=1:imax
if z1(i)< b && z1(i)> a %&& bw4(round(y1(i)),round(x1(i))) == 0
c(i) = 1;
c1(round(y2(i)),round(x1(i)))=1;
else
c(i)= 0;
c1(round(y2(i)),round(x1(i)))=0;
end
end
C=[c c c];
%c(find(c==0)) = NaN;
%contourf(X,Y,griddata(x1,y1,c,X,Y),100,'EdgeColor', 'None')
figure(1), scatter(x1,y1,3,z1,'filled');
axis equal
axis ([0 8000 0 12000])
axis off
figure(2), scatter(x1,y1,3,C,'filled');
axis equal
axis ([0 8000 0 12000])
axis off
se=strel('disk',50,8);
bw2=imdilate(c1,se);
bw4=bwlabel(bw2);
bw3=imfill(bw4,'holes');
max(bw4(:));
figure(3),imshow(c1,'InitialMagnification', 10);
figure(4), imshow(bw2,'InitialMagnification', 10);
figure(5), imshow(bw3,'InitialMagnification', 10);
figure(6),imshow(label2rgb(bw4),'InitialMagnification', 10);
k=ones(max(bw4(:)),1);
clear bw5
for i=1:length(x1)
if bw3(round(y2(i)),round(x1(i))) ~= 0
m=bw3(round(y2(i)),round(x1(i)));
bw5{m}(k(m),1)=x1(i); bw5{m}(k(m),2)=y2(i);
k(m)=k(m)+1;
end
end
figure(7), imshow(~c1,'InitialMagnification', 10);
hold on
for i=1:max(bw4(:))
%scatter(bw5{i}(:,1),bw5{i}(:,2))
j = boundary(bw5{i}(:,1),bw5{i}(:,2),0.5);
%poly=convhull(bw5{i}(:,1),bw5{i}(:,2));
%plot(bw5{i}(poly,1),bw5{i}(poly,2)), title('convhull')
if polyarea(bw5{i}(j,1),bw5{i}(j,2))> 10^5;
patch(bw5{i}(j,1),bw5{i}(j,2),'r'), title('boundary')
indexminy(i)=find(min(bw5{i}(:,2)) == bw5{i}(:,2));
indexminx(i)=find(min(bw5{i}(:,1)) == bw5{i}(:,1));
indexmaxy(i)=find(max(bw5{i}(:,2)) == bw5{i}(:,2));
indexmaxx(i)=find(max(bw5{i}(:,1)) == bw5{i}(:,1));
%xmin = bw5{i}(indexminx); xmax = bw5{i}(indexmaxx);
%ymin = bw5{i}(indexminy); ymax = bw5{i}(indexmaxy);
str=[(indexminx(i)+indexmaxx(i))/2,(indexminy(i)+indexmaxy(i))/2,'Region no.',num2str(i)];
text((min(x1(i))+max(x1(i)))/2,(min(y1(i))+max(y1(i)))/2,str)
polya(i)=polyarea(bw5{i}(j,1),bw5{i}(j,2));
end
end
spolya=sum(polya(:))
print -dpng -r500 B
Just to show you more pictures of when I fuse several of them:
And when I fuse:
As you can see they overlap, which I don't want, so I want each image that I create to 'know' what I'm doing on the previous runs so that it doesn't overlap. I want to get the percentage area of each region and if they overlap I cannot use the actual total area of my sample and the results are wrong.
I dont have my matlab working but here is what you need to do.
For the first run make an array of zeros equal to your image size
already_taken = zeros(size(bw3));
Then on each run, you can fill up the regions taken by this iteration. So at the end of your code, where you save the output to a png, read it back into something like
this_png = rgb2gray(imread(current_png_path))>threshold;
Convert this into a logical array by doing some thresholding and add these values into already taken. So at the end of the code, do a
already_taken = already_taken | this_png; % You might need to check if you need a single | or a double ||
So now you have an image of already taken pixels, ill bake sure I don't allow bw2 to take these values at first place
bw2(already_taken) = 0;
And at the end of the code when I want to write my png, my smart boundary creation might again have entered into already_taken area so there again I'll have to put some check. As far as I understand, this boundary is being created based upon your bw5. So where ever you fill this matrix, try putting a similar check as I did above for bw2.
I hope this helps.

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