I have an application which analyses data messages in a piped format (HL7), and for that, it has a DataGridView that's synced with a RichTextBox. Specifically, when you click on a property in the DataGridView, it jumps to the corresponding position in the RichTextBox, and vice versa.
The RichTextBox has word wrap disabled, so I can easily match the lines in the editor to the lines in the actual data.
However, I currently have to deal with messages containing a Base64 dump of a binary file in some parts of it, and the large content is making the rich text box wrap the lines anyway. This makes the calculations mess up, and when matching the returned position in the actual message text, I get wrong data, the analysis fails, and, usually, I get an ArgumentOutOfRangeException when the actual next line is shorter than the clicked position on that line.
This is the code:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
Int32 currentLine = this.rtxtMessage.GetLineFromCharIndex(selectionStart);
Int32 currentPos = selectionStart - this.rtxtMessage.GetFirstCharIndexFromLine(currentLine);
return new Point(currentPos, currentLine);
}
Correct behaviour:
On this click, the function will return point [28, 4].
Incorrect behaviour on force-wrapped line:
On this click, the function will return point [6,5], where it should actually be [2813,4]. This causes it to show analysis for the next line, and, as mentioned, if the click is on a location in the line that's beyond the end of that next analysed line, it causes an ArgumentOutOfRangeException.
Is there any way to compensate for this forced line split? I need to be able to accurately determine the position in the actual text to do the analysis.
Note, the line splits don't seem to be predictable; I don't know what the maximum length is after which it tries to split, or the characters on which it decides a split is possible.
Also note, the two called RichTextBox functions, namely GetLineFromCharIndex and GetFirstCharIndexFromLine, correctly correspond to what's actually shown on the screen... but what's shown on the screen is an incorrect representation of the real data. In fact, it doesn't even correspond to the output of the RichTextBox's own .Lines property, which gives me the content in an array of plain text lines.
I'd rather avoid using that .Lines property though, since I've noticed that in general, the functions to extract text from the rich text box are rather slow.
It appears that if you enable the richedit control's advanced typography option via sending the EM_SETTYPOGRAPHYOPTIONS message that the forced wrapping of long text lines does not occur with the RichTextBox.WordWrap property set to false.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
richTextBox1.HandleCreated += RTBHandledCreated;
FillRTB();
}
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
private void RTBHandledCreated(object sender, EventArgs e)
{
const Int32 WM_USER = 0x400;
const Int32 EM_SETTYPOGRAPHYOPTIONS = WM_USER + 202;
const Int32 EM_GETTYPOGRAPHYOPTIONS = WM_USER + 203;
const Int32 TO_ADVANCEDTYPOGRAPHY = 1;
const Int32 TO_SIMPLELINEBREAK = 2;
SendMessage(richTextBox1.Handle, EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
}
private void FillRTB()
{
for (Int32 i = 0; i <= 3; i++)
{
richTextBox1.AppendText($"Line {i}: ");
if (i == 1 || i == 3 )
{
StringBuilder sb = new StringBuilder(100000);
for (Int32 j = 0; j < sb.Capacity; j += 10)
{
for (Int32 k = 0; k <= 9; k++)
{
sb.Append(k.ToString());
}
}
richTextBox1.AppendText(sb.ToString());
}
if (i != 3)
{
richTextBox1.AppendText($"{Environment.NewLine}");
}
}
richTextBox1.SelectionStart = 0;
}
private void richTextBox1_SelectionChanged(object sender, EventArgs e)
{
label1.Text = richTextBox1.GetLineFromCharIndex(richTextBox1.SelectionStart).ToString();
}
}
Note that I originally mentioned in a comment to the OP's self-answer that a solution using the "Text Object Model" was possible. On more thorough testing of this technique, I found that it was only accurate for the first line of forced wrapped text, after that it included previous wrapped lines in the determining the line position. Therefore, I am not showing that method.
In my search for solutions, I found that, as I feared, RichTextBox.Lines is not a simple harmless pointer to an array, but in fact an elaborate operation to convert the rich text box contents to plain text, and using that in this situation would severely impact performance.
However, this made me realise that a lot of my code throughout the project was already accessing that property for random operations, especially in the analysis part that occurs on any line change.
I decided to make a cache variable for that lines array, which is cleared in the TextChanged event of the RichTextBox. All instances that fetched the lines on the RichTextBox were then replaced by a call to this little function:
private String[] GetTextboxLines()
{
if (this.m_LineCache != null)
return this.m_LineCache;
String[] lines = this.rtxtMessage.Lines;
this.m_LineCache = lines;
return lines;
}
This is still rather heavy when simply typing text into the rich text editor, since any key stroke will basically clear the array and then the analyser operation will fetch it again, but since the tool is an analyser first, and only an editor second, this is not a huge issue. And even then, RichTextBox.Lines was called multiple times in the analysis following such a key stroke, so overall the result is still vastly optimised.
With this system in place, the use of the lines array became viable again in my little GetCursorPosition() function, so I adapted it to make use of the new cached value as well:
/// <summary>Gets the cursor position as Point, with Y as line number and X as index on that line.</summary>
/// <returns>The cursor position as Point, with Y as line number and X as index on that line</returns>
protected Point GetCursorPosition()
{
Int32 selectionStart = this.rtxtMessage.SelectionStart;
String[] lines = this.GetTextboxLines();
Int32 nrOfLines = lines.Length;
Int32 y;
for (y = 0; y < nrOfLines; y++)
{
Int32 lineLen = lines[y].Length;
// Can be equal if at the very end of a line.
if (selectionStart <= lineLen)
return new Point(selectionStart, y);
// +1 to compensate for the line break character,
// which is only one byte in a rich text box.
selectionStart -= (lineLen + 1);
}
return new Point(0, nrOfLines - 1);
}
Related
What I am doing is reading logfile records into console.
I want to keep a progressbar in the bottom and shows progress.
Problem is: the updating record will override the progressbar.
private static void DrawProgressBar(int complete, int maxVal, int barSize, char progressCharacter)
{
Console.CursorVisible = false;
int left = Console.CursorLeft;
//int top = Console.CursorTop;
//Console.CursorTop = Console.WindowTop + Console.WindowHeight - 1;
decimal perc = (decimal)complete / (decimal)maxVal;
int chars = (int)Math.Floor(perc / ((decimal)1 / (decimal)barSize));
string p1 = String.Empty, p2 = String.Empty;
for (int i = 0; i < chars; i++) p1 += progressCharacter;
for (int i = 0; i < barSize - chars; i++) p2 += progressCharacter;
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(p1);
Console.ForegroundColor = ConsoleColor.DarkGreen;
Console.Write(p2);
Console.ResetColor();
Console.Write(" {0}%", (perc * 100).ToString("N2"));
Console.CursorLeft = left;
//Console.SetCursorPosition(left, top);
}
You need to set the cursor to the correct position.
Here is a simple example of SetCursorPosition:
for (int i = 0; i < 10; i++) {
Console.SetCursorPosition(0, 0);
Console.Write("i = {0}", i);
System.Threading.Thread.Sleep(1000);
}
Before you print the progressbar you need to find out where to start and resetting the cursor before writing the progressbar.
There's an easy way to do this and several very difficult ways to do this.
The easy way is to re-display the progress bar after every call to Console.WriteLine. That way it doesn't matter if a scrolled line overwrites the progress bar, because you're just going to re-display it.
If you want to get fancy, you need to handle scrolling yourself. That means creating a TextWriter descendant that sees you writing to the last line (where the status line is), and , so that whenever some code wants to write to the bottom line where the progress bar is, you scroll the rest of the screen and write the new line in the vacated spot.
There are several problems with the second approach, the first being that in order to handle your own scrolling you have to call the Windows API; there is no support in the .NET BCL for manipulating the console screen buffer. I have code you can use to do it (see http://mischel.com/toolutil/consoledotnet.zip), but it's kind of involved. Plus, you have additional problems if the line you're writing is wider than the current window width. If the line wraps, then it'll overwrite your progress bar.
It really is simpler to just re-draw the status bar after every line, or perhaps group of lines if you're writing multiple lines at a time.
I have a bunch of text that needs to change its font size. The text block I want is in the middle of a document.
So right now, what I do is below. I keep getting some bunches of text not changing font size. Is there a better way to do this? I tried going word by word but that takes visibly long time to complete. Also trying the find whole text via TxControlObj.Find method gets Out Of Memory Errors.
// processedText is the block that needs to have the changed font size
while (processedText != String.Empty)
{
if (processedText.Length > 50)
processRange = 50;
else
processRange = processedText.Length;
startPos = TxControlObj.Find(processedText.Substring(0, processRange), startPos, TXTextControl.FindOptions.NoMessageBox);
TxControlObj.Selection.FontSize = fontSize;
processedText = processedText.Remove(0, processRange);
}
//This last bit here is because, I get highlighted text at the end of the loop otherwise.
startPos = TxControlObj.Find("", startPos, TXTextControl.FindOptions.NoMessageBox);
TxControl.Update();
I have a matrix of bits 20x23.
I need to represent this matrix in a winform (GUI).
The idea is that the user will be able to change the content of specific cell by clicking the relevant button, that represent the specific cell in the matrix.
(When the user click a button, the relevant bit cell in the matrix is being inverted)
I have considered using GRID for this, but due to GUI (Design) issue, it is not possible to use it.
How can I create and manage 20x23 (=460) buttons effectively and keep it correlated to the real matrix ?
It is not that difficult, I would start with a method that will generate a button matrix for you. This matrix consists of Buttons, where the ID (ie. Tag) will correspond to the correct cellNumber (you might consider passing the coordinates as a Point instance as well, I will leave that up for you to decide).
So basically, it comes to this, where all the buttons are rendered on a panel (panel1):
...
#region Fields
//Dimensions for the matrix
private const int yDim = 20;
private const int xDim = 23;
#endregion
...
private void GenerateButtonMatrix()
{
Button[,] buttonMatrix = new Button[yDim, xDim];
InitializeMatrix(ref matrix); //Corresponds to the real matrix
int celNr = 1;
for (int y = 0; y < yDim; y++)
{
for (int x = 0; x < xDim; x++)
{
buttonMatrix[y,x] = new Button()
{
Width = Height = 20,
Text = matrix[y, x].ToString(),
Location = new Point( y * 20 + 10,
x * 20 + 10), // <-- You might want to tweak this
Parent = panel1,
};
buttonMatrix[y, x].Tag = celNr++;
buttonMatrix[y,x].Click += MatrixButtonClick;
}
}
}
As you can see, all 460 buttons have a custom EventHandler connected to the ClickEvent, called MatrixButtonClick(). This eventhandler will handle the ClickEvent and may determine on which button the user has clicked. By retrieving the tag again, you may calculate the correct coordinate which corresponds to the 'real' matrix.
private void MatrixButtonClick(object sender, EventArgs e)
{
if (sender is Button)
{
Button b = sender as Button;
//The tag contains the cellNr representing the cell in the real matrix
//To calculate the correct Y and X coordinate, use a division and modulo operation
//I'll leave that up to you :-)
.... Invert the real matrix cell value
}
}
I will not give away everything, since it is a nice practice for you to achieve :).
I would:
1) create an object with needed properties
2) fill a list and fill with values
3) iterate list creating buttons and assigning its click handler and button's name (for name something like button_rowindex_colindex)
4) inside click handler, assign value to object cell by detecting which button was clicked
There are a lot of questions (EG: 1, 2, 3, 4, 5) asking how you can truncate a string to a desired amount of characters. But I want a piece of text truncated to fit in a container. (IE: crop the string by it's width in pixels, not characters).
This is easy if you use WPF, but not so much in WinForms...
So: how can you truncate a string to make it fit in a container?
After a day of coding I found a solution and I wanted to share it with the community.
First of all: there is no native truncate function for a string or winforms TextBox. If you use a label, you can use the AutoEllipsis property.
FYI: An ellipsis is a punctuation mark that consist of three dots. IE: …
That's why I made this:
public static class Extensions
{
/// <summary>
/// Truncates the TextBox.Text property so it will fit in the TextBox.
/// </summary>
static public void Truncate(this TextBox textBox)
{
//Determine direction of truncation
bool direction = false;
if (textBox.TextAlign == HorizontalAlignment.Right) direction = true;
//Get text
string truncatedText = textBox.Text;
//Truncate text
truncatedText = truncatedText.Truncate(textBox.Font, textBox.Width, direction);
//If text truncated
if (truncatedText != textBox.Text)
{
//Set textBox text
textBox.Text = truncatedText;
//After setting the text, the cursor position changes. Here we set the location of the cursor manually.
//First we determine the position, the default value applies to direction = left.
//This position is when the cursor needs to be behind the last char. (Example:"…My Text|");
int position = 0;
//If the truncation direction is to the right the position should be before the ellipsis
if (!direction)
{
//This position is when the cursor needs to be before the last char (which would be the ellipsis). (Example:"My Text|…");
position = 1;
}
//Set the cursor position
textBox.Select(textBox.Text.Length - position, 0);
}
}
/// <summary>
/// Truncates the string to be smaller than the desired width.
/// </summary>
/// <param name="font">The font used to determine the size of the string.</param>
/// <param name="width">The maximum size the string should be after truncating.</param>
/// <param name="direction">The direction of the truncation. True for left (…ext), False for right(Tex…).</param>
static public string Truncate(this string text, Font font, int width, bool direction)
{
string truncatedText, returnText;
int charIndex = 0;
bool truncated = false;
//When the user is typing and the truncation happens in a TextChanged event, already typed text could get lost.
//Example: Imagine that the string "Hello Worl" would truncate if we add 'd'. Depending on the font the output
//could be: "Hello Wor…" (notice the 'l' is missing). This is an undesired effect.
//To prevent this from happening the ellipsis is included in the initial sizecheck.
//At this point, the direction is not important so we place ellipsis behind the text.
truncatedText = text + "…";
//Get the size of the string in pixels.
SizeF size = MeasureString(truncatedText, font);
//Do while the string is bigger than the desired width.
while (size.Width > width)
{
//Go to next char
charIndex++;
//If the character index is larger than or equal to the length of the text, the truncation is unachievable.
if (charIndex >= text.Length)
{
//Truncation is unachievable!
//Throw exception so the user knows what's going on.
throw new IndexOutOfRangeException("The desired width of the string is too small to truncate to.");
}
else
{
//Truncation is still applicable!
//Raise the flag, indicating that text is truncated.
truncated = true;
//Check which way to text should be truncated to, then remove one char and add an ellipsis.
if (direction)
{
//Truncate to the left. Add ellipsis and remove from the left.
truncatedText = "…" + text.Substring(charIndex);
}
else
{
//Truncate to the right. Remove from the right and add the ellipsis.
truncatedText = text.Substring(0, text.Length - charIndex) + "…";
}
//Measure the string again.
size = MeasureString(truncatedText, font);
}
}
//If the text got truncated, change the return value to the truncated text.
if (truncated) returnText = truncatedText;
else returnText = text;
//Return the desired text.
return returnText;
}
/// <summary>
/// Measures the size of this string object.
/// </summary>
/// <param name="text">The string that will be measured.</param>
/// <param name="font">The font that will be used to measure to size of the string.</param>
/// <returns>A SizeF object containing the height and size of the string.</returns>
static private SizeF MeasureString(String text, Font font)
{
//To measure the string we use the Graphics.MeasureString function, which is a method that can be called from a PaintEventArgs instance.
//To call the constructor of the PaintEventArgs class, we must pass a Graphics object. We'll use a PictureBox object to achieve this.
PictureBox pb = new PictureBox();
//Create the PaintEventArgs with the correct parameters.
PaintEventArgs pea = new PaintEventArgs(pb.CreateGraphics(), new System.Drawing.Rectangle());
pea.Graphics.PageUnit = GraphicsUnit.Pixel;
pea.Graphics.PageScale = 1;
//Call the MeasureString method. This methods calculates what the height and width of a string would be, given the specified font.
SizeF size = pea.Graphics.MeasureString(text, font);
//Return the SizeF object.
return size;
}
}
Usage:
This is a class you can copy and paste in the namespace that contains your winforms form. Make sure you include "using System.Drawing;"
This class has two extensions methods, both called Truncate. Basically you can now do this:
public void textBox1_TextChanged(object sender, EventArgs e)
{
textBox1.Truncate();
}
You can now type something in textBox1 and if needed, it will automatically truncate your string to fit in the textBox and it will add an ellipsis.
Overview:
This class currently contains 3 methods:
Truncate (extension for TextBox)
Truncate (extension for string)
MeasureString
Truncate (extension for TextBox)
This method will automatically truncates the TextBox.Text property. The direction of truncation is determent by the TextAlign property. (EG: "Truncation for left alignm…", "…ncation for right alignment".) Please note: this method might need some altering to work with other writing systems such as Hebrew or Arabic.
Truncate (extension for string)
In order to use this method you must pass two parameters: a font and a desired width. The font is used to calculate the width of the string and the desired width is used as the maximum width allowed after truncation.
MeasureString
This method is private in the code snippet. So if you want to use it, you must change it to public first. This method is used to measure the height and width of the string in pixels. It requires two parameters: the text to be measured and the font of the text.
I hope I helped someone with this. Perhaps there is an other way to do this, I found this answer by Hans Passant, which truncates a ToolTipStatusLabel, which is quite impressive. My .NET skills are nowhere near that of Hans Passant so I haven't managed to convert that code to work with something like a TextBox... But if you did succeeed, or have another solution I would love to see it! :)
I tested the code of Jordy and compared the result with this code of mine, there is no difference, they both trim/truncate fairly OK but not well in some cases, that may be the size measured by MeasureString() is not exact. I know this code is just a simplified version, I post it here if someone cares about it and uses it because it's short and I tested: there is no difference in how exactly this code can trim/truncate string compared with the Jordy's code, of course his code is some kind of full version with 3 methods supported.
public static class TextBoxExtension
{
public static void Trim(this TextBox text){
string txt = text.Text;
if (txt.Length == 0 || text.Width == 0) return;
int i = txt.Length;
while (TextRenderer.MeasureText(txt + "...", text.Font).Width > text.Width)
{
txt = text.Text.Substring(0, --i);
if (i == 0) break;
}
text.Text = txt + "...";
}
//You can implement more methods such as receiving a string with font,... and returning the truncated/trimmed version.
}
Can somebody please explain how I would go about measuring the string inside a richtextbox control so that the I can automatically resize the richtextbox control according to its content?
Thank you
Edit:
I've thought about it, and since the below answer won't work if there are different fonts in the RichTextBox Control, what if, I could get the upper-left coords of the richtextbox control and then get the bottom-right coords of the very last line of text inside the rtb. That would essentially give me the Width and Height of the string inside the RichTextBox Control. Is this possible? Or is this a bad idea to do it this way?
Put the following code in the ContentsResized event:
Private Sub rtb_ContentsResized(ByVal sender As Object, ByVal e As System.Windows.Forms.ContentsResizedEventArgs) Handles txtQuestion.ContentsResized
Dim h = e.NewRectangle.Height, w = e.NewRectangle.Width
h = Math.Max(h, sender.Font.Height)
h = Math.Min(h, Me.ClientSize.Height - 10 - sender.Top)
h += sender.Height - sender.ClientSize.Height + 1
sender.Height = h
End Sub
Assuming that someone is typing into the control, you could use an event to fire every time a character is entered (increment counter) and decrement when it is deleted. This would give you a true count.
Edit:
Have you tried this to adjust the height?
richTextBox1.Height = (int)(1.5 * richTextBox1.Font.Height) + richTextBox1.GetLineFromCharIndex(richTextBox1.Text.Length + 1) * richTextBox1.Font.Height + 1 + richTextBox1.Margin.Vertical;
richTextBox1.SelectionStart = 0;
richTextBox1.SelectionStart = richTextBox1.Text.Length;
Or you can do this using Width:
Graphics g = Graphics.FromHwnd(richTextBox1.Handle);
SizeF f = g.MeasureString(richTextBox1.Text, richTextBox1.Font);
richTextBox1.Width = (int)(f.Width)+5;
Try calling GetPreferredSize(Size.Empty). It is defined in the Control class, and if overriden property by the RichTextBoxControl, ought to give you what you are looking for.
If you pass something other than Size.Empty into the method, then it will use that value as a maximum constraint. Using Size.Empty means that the potential size is unbounded.
You can measure a string by calling TextRenderer.MeasureText.
However, if the text contains multiple fonts, this will not work.
EDIT: You're looking for the GetPositionFromCharIndex method.
Note that if there are multiple lines, you should take the max of the X coordinates of the last character on each line.
I found a solution for the Rich text box height issues.. i have modified it a for general use..
Create following structs in your application....
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public Int32 left;
public Int32 top;
public Int32 right;
public Int32 bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLBARINFO {
public Int32 cbSize;
public RECT rcScrollBar;
public Int32 dxyLineButton;
public Int32 xyThumbTop;
public Int32 xyThumbBottom;
public Int32 reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public Int32[] rgstate;
}
Create following private variables in your class for form (where ever you need to calculate rich text height)
private UInt32 SB_VERT = 1;
private UInt32 OBJID_VSCROLL = 0xFFFFFFFB;
[DllImport("user32.dll")]
private static extern
Int32 GetScrollRange(IntPtr hWnd, UInt32 nBar, out Int32 lpMinPos, out Int32 lpMaxPos);
[DllImport("user32.dll")]
private static extern
Int32 GetScrollBarInfo(IntPtr hWnd, UInt32 idObject, ref SCROLLBARINFO psbi);
Add following method to your Class for form
private int CalculateRichTextHeight(string richText) {
int height = 0;
RichTextBox richTextBox = new RichTextBox();
richTextBox.Rtf = richText;
richTextBox.Height = this.Bounds.Height;
richTextBox.Width = this.Bounds.Width;
richTextBox.WordWrap = false;
int nHeight = 0;
int nMin = 0, nMax = 0;
SCROLLBARINFO psbi = new SCROLLBARINFO();
psbi.cbSize = Marshal.SizeOf(psbi);
richTextBox.Height = 10;
richTextBox.ScrollBars = RichTextBoxScrollBars.Vertical;
int nResult = GetScrollBarInfo(richTextBox.Handle, OBJID_VSCROLL, ref psbi);
if (psbi.rgstate[0] == 0) {
GetScrollRange(richTextBox.Handle, SB_VERT, out nMin, out nMax);
height = (nMax - nMin);
}
return height;
}
You may need to modify above method to make it work as per your requirement...
Make sure to send Rtf string as parameter to method not normal text and also make sure to assign available width and height to the Richtextbox variable in the method...
You can play with WordWrap depending on your requirement...
Add on to bathineni's great answer:
Background: I needed to measure RTF output height for rendering onto paper and because I have custom dynamic page headers/footers I needed to control paging).
(RichTextBox.GetLineFromCharIndex let me down because of complex RTF; including lines & multi column Tables with wordwrap).
Anyhow all was working fine, until someone else used my app with the dreaded windows "Make text and other items larger or smaller" (DPI settings.) - in short now measuring bigger sized fonts it screwed up the page length calculations. (the printer still rendered the text and columns correctly - only the page lengths were now all wrong.)
Only factoring DPI difference failed as in short bigger text didn't fit properly into source RTF tx and cellx values.
Anyhow, in case others are doing similar crazy things bit of trial and error came up with the following (eventually very few) mods to the bathineni CalculateRichTextHeight method:
RichTextBox richTextBox = new RichTextBox(); // same as original
int dpix = richTextBox.CreateGraphics().DpiX; // get dpi
richTextBox.WordWrap = true; // I needed this, you many not
// ... set size etc - same as original answer
richTextBox.Scale(new System.Drawing.SizeF(dpix / 96, dpix / 96)); // scale RTB
// ...
// 96? my original calculations based on windows default 96dpi settings.
Seems the otherwise obscure Control.Scale(sizef) is actually useful for something after all.
Note: if converting results to actual printed lines, (in my case all my \pard's were "\sl-240\slmult0" which comes out to 16 (pix?) per line) also remember to re-factor the divisor.
i.e. in my case:
lines = height / (int)(16 * (dpix / 96))