I have noticed the following problem when implementing a Word-like application:
The QRasterizer in Qt skips lines when they have a thickness smaller than 1.0f. I am running into this situation when zooming out in my word editor application. The Y values of the two line points then get as small as this:
y1 = 290.32812500000000
y2 = 290.92187500000000
When rendering the line Qt skips it. I have tracked this down to the following code in QRasterizer::rasterize(), where min_y and max_y are the two above Y values times 64 (fixed point values):
int iTopBound = qMax(d->clipRect.top(), int((min_y + 32 + COORD_OFFSET - COORD_ROUNDING) >> 6));
int iBottomBound = qMin(d->clipRect.bottom(), int((max_y - 32 + COORD_OFFSET - COORD_ROUNDING) >> 6));
if (iTopBound > iBottomBound)
return;
Since min_y is rounded upwards and max_y is rounded downwards it runs into the IF condition and thus returns without performing any rendering.
I can workaround this problem by enabling anti-aliasing, which however results in the rendering getting brighter when zooming out. What I really need is a behavior like in Microsoft Word: no matter how far you zoom out, a black rectangle always stays visible as black rectangle on screen.
Using a cosmetic pen would solve the problem but doesn't work together with a customizable line thickness, which I need to support as well.
Any ideas how to workaround this problem?
Greetings,
Fabian
Related
This is a tricky question to search. I'm drawing a line in HLSL, and I want to define the opacity of the line based on the horizonal coordinate of each pixel.
Picture a vertical line, with 10 pixel width. The left and right most pixels should be nearly fully transparent, the center pixels fully opaque, and a falloff somewhere in between. In my shader, I will have the "horizontal coordinates" as a factor 0->1, where 0 is left-most and 1 is right-most, and I'll have a single "feather" factor, also 0->1, which should make the line softer on the edges when it is 1.0, or very sharp at 0.0.
I realize that a fully robust system might need more information, such as where to begin and end the falloff. But assume that I just want to draw standard lines that look good at varying widths, while supplying a single feather factor. The feather should increase the falloff range, where feather=0.5 should start the left-side falloff at about 0.25, and feather=1.0 would have the falloff starting at the center of the line.
This is what I came up with, after playing around with desmos.com/calculator for a while. x is the horizontal factor:
float alpha = min( 1.0, x * 40.0 * (1.0-x) * (1.0 - (feather-0.1)) );
It works decently, but I'm not happy with the falloff behavior with the way I'm using the feather value. One needs to crank the feather all the way up to 0.95 to get the falloff to start where it should be at around 0.5.
Here is a preview of how this looks. The circle and blue line have a feather of 0.95, and the neon green diagonal line has a feather of 0.5, and you can see it is far too rough. All of these lines have a width of 10.
Any math people out there have any advice for a simple or better way to do this? Feel free to completely change the formula if you have a better way to pull it off. But if possible, please post the slope formula with standard ascii characters. I'm not the greatest with math symbols.
There would be many ways to implement such function. A simple way can be:
Since the current alpha function you made is based on parabolla,
float alpha2 = 1.0f - pow((x - 0.5f) / 0.5f, 2.0f);
make a sharper fall-off function by using higher power.
float alpha32 = 1.0f - pow((x - 0.5f) / 0.5f, 32.0f);
And mix these two based on feather value.
float alpha = lerp(alpha32, alpha2, feather * feather);
I've noticed that whenever I use magenta.js's built in Visualizer method, it renders ever so slightly blurry (perhaps an anti-aliasing issue?) notes. I've attached an image:
I can see this with varying intensities across many of the documentation's examples as well, such as https://piano-scribe.glitch.me/. Is there a way I can get sharp edges or at the least minimize the blurriness? I'm not sure whether this issue has been addressed or is suitable in the magenta github, so I'm posting here.
Edit: with image-rendering: pixelated on the canvas element, zoomed in.
This is a bug (if you call it that) with magenta-js's visualizer. Taking a look at the redraw method in their source reveals that the x position and w(idth) of each note are determined with the following lines.
const x = (this.getNoteStartTime(note) * this.config.pixelsPerTimeStep) +
offset;
const w = (this.getNoteEndTime(note) - this.getNoteStartTime(note)) *
this.config.pixelsPerTimeStep;
Now, when drawing on a canvas, if you don't draw at an integer, the browser will interpolate and try to draw a close representation, resulting in the miscolored pixels you noticed.
All that's left to do is confirm that x and/or w are not integers. I loaded the demo page, opened the relevant js file in the sources tab, searched for this line and put a breakpoint.
Sure enough. x = 13.8 and w = 15.35999. I've submitted magenta-js#238 with a fix.
I need to get the actual height of QTextDocument in order to be able to set the containing QPlainTextEdit to a minimum height (while keeping its width constant) so that it shows the whole content without the vertical scrollbar. I tried to follow this question (closed with with accepted answer) How do I determine the height of a QTextDocument? but it does not do what it promises.
A piece of code:
from PyQt5.QtWidgets import QApplication, QPlainTextEdit
app = QApplication([])
w = QPlainTextEdit()
w.setPlainText("Hello!")
print(w.document().size())
w.setPlainText("Hello!\nHello again!")
print(w.document().size())
prints out:
PyQt5.QtCore.QSizeF(35.0, 1.0)
PyQt5.QtCore.QSizeF(64.0, 2.0)
It seems that the width is measured correctly in pixels but the height just shows the number of lines instead of pixels. I think multiplying it with font pixel metric height does not help because there can be mixed formatting (in general it can be a rich text / HTML) and line spacing, document margins and maybe some other complicated stuff based on implementation details... etc.
So is there a way out?
So I finally found a solution but it is really ugly. If anyone knows anything better, please publish it.
from PyQt5.QtWidgets import QApplication, QPlainTextEdit
app = QApplication([])
w = QPlainTextEdit()
# test various formatting
w.appendHtml("<h1>Hello!</h1>")
w.appendHtml("<b>Hello!</b>")
w.appendPlainText("Hello!")
doc = w.document()
layout = doc.documentLayout()
h = 0
b = doc.begin()
while b != doc.end():
h += layout.blockBoundingRect(b).height()
b = b.next()
# magic formula: I do not know why the document margin is already
# once included in the height of the last block, and I do not know
# why there must be the number 1 at the end... but it works
w.setFixedHeight(h + doc.documentMargin() + 2 * w.frameWidth() + 1)
w.show()
app.exec_()
So this should show the box without scroll bar. If you decrease the height by 1, the scroll bar appears. This should work with any number of lines, document margins, frame widths, formatting etc. Hopefully.
Shot in the dark without testing
Have you looked # pageSize?
From the docs:
This property holds the page size that should be used for laying out the document
The units are determined by the underlying paint device. The size is
measured in logical pixels when painting to the screen, and in points
(1/72 inch) when painting to a printer.
By default, for a newly-created, empty document, this property
contains an undefined size.
If you set the pageSize, as directed by the other thread, I'd expect you'd get the value out in the pixels that QPlainTextEdit::setMinimumHeight needs.
When using anti-aliasing rendering in Qt's QGraphicsScene, there is a behavior that makes drawings appear not as expected: overlapping lines become darker. I could not see any description of this behavior in the documentation, and I cannot find a way to disable it.
For example if I want to draw such a polygon:
Because of the number of points, it is impossible not to have overlapping lines - fine. But because anti-aliasing is activated, some borders appear 'thicker' than others.
Is there any way to avoid this and have anti-aliased lines that can overlap and yet at the same time be rendered without getting darker?
I know of course that I can redefine the paint() function and draw manually individual lines that do not overlap, but this is what I want to avoid. I am using Pyside and this would significantly slow down the application, due to the high frequency at which paint() is being called.
EDIT Fixed by defining the object shape using QPainterPath / QGraphicsPathItem instead of QPolygon / QGraphicsPolygonItem. In that case the moveTo function allows to avoid lines that overlap.
Another thing you could try is adding half a pixel to your coordinates (not dimensions). This fixed the anti-aliasing issue for me.
XCoord = int(XValue) + 0.5
YCoord = int(XValue) + 0.5
Also make sure that before that you have integer pixel values.
See also: Why is my image rotation algorithm not working?
This question isn't language specific, and is a math problem. I will however use some C++ code to explain what I need as I'm not experienced with the mathematic equations needed to express the problem (but if you know about this, I’d be interested to learn).
Here's how the image is composed:
ImageMatrix image;
image[0][0][0] = 1;
image[0][1][0] = 2;
image[0][2][0] = 1;
image[1][0][0] = 0;
image[1][1][0] = 0;
image[1][2][0] = 0;
image[2][0][0] = -1;
image[2][1][0] = -2;
image[2][2][0] = -1;
Here's the prototype for the function I'm trying to create:
ImageMatrix rotateImage(ImageMatrix image, double angle);
I'd like to rotate only the first two indices (rows and columns) but not the channel.
The usual way to solve this is by doing it backwards. Instead of calculating where each pixel in the input image ends up in the output image, you calculate where each pixel in the output image is located in the input image (by rotationg the same amount in the other direction. This way you can be sure that all pixels in the output image will have a value.
output = new Image(input.size())
for each pixel in input:
{
p2 = rotate(pixel, -angle);
value = interpolate(input, p2)
output(pixel) = value
}
There are different ways to do interpolation. For the formula of rotation I think you should check https://en.wikipedia.org/wiki/Rotation_matrix#In_two_dimensions
But just to be nice, here it is (rotation of point (x,y) angle degrees/radians):
newX = cos(angle)*x - sin(angle)*y
newY = sin(angle)*x + cos(angle)*y
To rotate an image, you create 3 points:
A----B
|
|
C
and rotate that around A. To get the new rotated image you do this:
rotate ABC around A in 2D, so this is a single euler rotation
traverse in the rotated state from A to B. For every pixel you traverse also from left to right over the horizontal line in the original image. So if the image is an image of width 100, height 50, you'll traverse from A to B in 100 steps and from A to C in 50 steps, drawing 50 lines of 100 pixels in the area formed by ABC in their rotated state.
This might sound complicated but it's not. Please see this C# code I wrote some time ago:
rotoZoomer by me
When drawing, I alter the source pointers a bit to get a rubber-like effect, but if you disable that, you'll see the code rotates the image without problems. Of course, on some angles you'll get an image which looks slightly distorted. The sourcecode contains comments what's going on so you should be able to grab the math/logic behind it easily.
If you like Java better, I also have made a java version once, 14 or so years ago ;) ->
http://www.xs4all.nl/~perseus/zoom/zoom.java
Note there's another solution apart from rotation matrices, that doesn't loose image information through aliasing.
You can separate 2D image rotation into skews and scalings, which preserve the image quality.
Here's a simpler explanation
It seems like the example you've provided is some edge detection kernel. So if what you want to is detect edges of different angles you'd better choose some continuous function (which in your case might be a parametrized gaussian of x1 multiplied by x2) and then rotate it according to formulae provided by kigurai. As a result you would be able to produce a diskrete kernel more efficiently and without aliasing.