I'm trying to understand how the JavaFX canvas setStroke method works. It doesn't set the color of pixels to the desired value.
No problem with the setFill method though.
Canvas canvas = new Canvas(500, 500);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
int x = 10;
int y = 10;
printPixelRGB(x, y); // displays: red=255, green=0, blue=0 -> OK
// scenario 1: using fill-methods
gc.setStroke(Color.WHITE);
gc.strokeRect(x, y, 200, 200);
printPixelRGB(x, y); // displays: red=255, green=191, blue=191 -> ????
// scenario 2: using stroke-methods
gc.setFill(Color.WHITE);
gc.fillRect(x, y, 200, 200);
printPixelRGB(x, y); // displays: red=255, green=255, blue=255 -> OK
private void printPixelRGB(int x, int y) {
WritableImage snap = gc.getCanvas().snapshot(null, null);
int color = snap.getPixelReader().getArgb(x, y);
int red = (color >> 16) & 0xff;
int green = (color >> 8) & 0xff;
int blue = color & 0xff;
System.out.printf("red=%-3d, green=%-3d, blue=%-3d \n", red, green, blue);
} // printPixelRGB()
The result from scenario 2 is as expected.
The result from scenario 1 on the other hand is very strange: the pixel is not completely white!! How come?
How can I fix this?
Thank you
This is the effect of anti-aliasing.
The pixel is not 100% covered by the line. Therefore the resulting color is interpolated between the pixel color before the drawing operation and the stroke color.
The following code allows you to observe the effect:
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas(800, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.RED);
gc.fillRect(0, 0, 800, 300);
gc.setStroke(Color.WHITE);
final double dx = 0;
for (double x = 10, s = 1; x < (800 - 40); s++, x += 50) {
gc.setLineWidth(s);
gc.strokeLine(x + dx, 0, x + dx, 300);
}
WritableImage snap = canvas.snapshot(null, null);
PixelReader reader = snap.getPixelReader();
for (int x = 10; x < (800 - 40); x += 50) {
Color color = reader.getColor(x, 150);
System.out.printf("red=%-3d, green=%-3d, blue=%-3d\n", (int) (color.getRed() * 0xFF),
(int) (color.getGreen() * 0xFF), (int) (color.getBlue() * 0xFF));
}
primaryStage.setScene(new Scene(new StackPane(canvas)));
primaryStage.show();
}
The first line drawn covers the x interval of [10-lineWidth/2, 10+lineWidth/2] = [9.5, 10.5]. The column with index 10 is only half covered which is why the resulting color is ((255, 0, 0) + (255, 255, 255)) / 2 = (255, 127, 127) (already rounded to integral values)
Modify dx to cover the column completely and you get the stroke color:
final double dx = 0.5;
The pixel is covered completely by the rectangle you fill in your code and the color is set to the fill color for this reason. Add 0.5 to one of the starting coordinates of the rectangle you fill, and you'll observe a similar effect as with the stroke.
Related
I want to get nested stencils to work in OpenGL.
I call them masks from now on.
So the stencil buffer is cleared to all 0's.
I make my first mask, the grey area.
Now that have to be all 1's in the stencil buffer, and normal drawing is not allowed to happen outside that mask.
Now I create a second mask which is a child of the first one:
It would only update the stencil buffer if the area is inside the area of the previous mask:
Same if we would make another child mask, we would only increment if it was inside the parent mask.
Now if we would end the last mask (the green one), we can decrement the values where they where equal to the mask depth.
Ok that is the idea, but I have been stuck on getting this to work for weeks (not full time...).
Either masking does not work, or it does not work as expected. Here I provide code that makes the most sense to me.
I use processing, which can be downloaded here https://processing.org/ (download and run, simple as that).
PGL pgl;
Rect current_rect;
void setup() {
size(600, 600, P3D);
noStroke();
// will have a depth of 0, equal to the stencil buffer after a clear
current_rect = make_rect(null, 0, 0, width, height);
}
class Rect {
float x;
float y;
float w;
float h;
ArrayList<Rect> children = new ArrayList<Rect>();
Rect parent;
int mask_depth;
}
Rect make_rect(Rect parent, float x, float y, float w, float h) {
Rect r = new Rect();
r.x = x;
r.y = y;
r.w = w;
r.h = h;
if (parent != null) {
r.parent = parent;
r.mask_depth = parent.mask_depth + 1;
parent.children.add(r);
}
return r;
}
void draw() {
if (frameCount == 2) noLoop();
println();
pgl = beginPGL();
pgl.enable(PGL.STENCIL_TEST);
pgl.clear(PGL.STENCIL_BUFFER_BIT);
background(150, 80, 70);
begin_mask(100, 100, 400, 400);
fill(150);
rect(100, 100, 400, 400);
// SHOULD NOT FALL OUTSIDE OF THE MASK!
debug_text(" A ");
// disabled for now cause the above begin_mask
// goes already wrong
if (false) {
begin_mask(50, 150, 500, 100); // B
fill(100);
rect(50, 150, 500, 100);
debug_text(" B ");
if (true) {
begin_mask(200, 50, 100, 500); // C
fill(50);
rect(200, 50, 100, 500);
debug_text(" C ");
end_mask(); // C
}
end_mask(); // B
// why is the one from above (C) on this one?
// disable this one to see what I mean
if (true) {
begin_mask(50, 350, 500, 100); // D
fill(75);
rect(50, 350, 500, 100);
fill(255);
debug_text(" D ");
end_mask(); // D
}
}
end_mask(); // A
flush();
endPGL();
}
void begin_mask(float x, float y, float w, float h) {
current_rect = make_rect(current_rect, x, y, w, h);
flush();
pgl.colorMask(false, false, false, false);
pgl.depthMask(false);
pgl.stencilOp(PGL.KEEP, PGL.KEEP, PGL.INCR);
println("increment stencil when stencil is equal to: "+current_rect.parent.mask_depth);
pgl.stencilFunc(PGL.EQUAL, current_rect.parent.mask_depth, 0xFF);
// write to stencil buffer
noStroke();
fill(0);
rect(x, y, w, h);
flush();
enable_normal_draw_mode();
}
void enable_normal_draw_mode() {
pgl.stencilMask(0x00);
println("normal write when stencil depth is: "+(current_rect.mask_depth));
pgl.stencilFunc(PGL.GEQUAL, current_rect.mask_depth, 0xff);
pgl.stencilOp(PGL.KEEP, PGL.KEEP, PGL.KEEP);
pgl.colorMask(true, true, true, true);
pgl.depthMask(true);
flush();
println("enable_normal_draw_mode "+current_rect.mask_depth);
}
void end_mask() {
// decrement stencil mask as if we never existed
pgl.stencilMask(0xff);
pgl.stencilFunc(PGL.GEQUAL, current_rect.mask_depth, 0xff);
pgl.stencilOp(PGL.KEEP, PGL.KEEP, PGL.DECR);
pgl.colorMask(false, false, false, false);
pgl.depthMask(false);
noStroke();
fill(0);
rect(current_rect.x, current_rect.y, current_rect.w, current_rect.h);
flush();
current_rect = current_rect.parent;
enable_normal_draw_mode();
}
void debug_text(String s) {
fill(255);
text(xxx(s), 0, 0);
}
String xxx(String to_repeat) {
String result = "";
String line = "";
for (int x = 0; x < 50; x++) {
line += to_repeat;
}
for (int y = 0; y < 50; y++) {
result += line + "\n";
}
return result;
}
I'm using biojava to create a chromatogram.
The chromatogram is an image. Each basecall is held within a rectange.
To get x coordinate (left side): gfx.getCallboxBounds(int i).getX();
To get width: gfx.getCallboxBounds(int i).getX()
where the integer i represents the rectangle in the arrangement of rectangles that build the chromatogram.
To get the confidence value of a given base:
(I create an array called confidence) confidence[i - 1];
where i is, again, the base in question.
Confidence values are reported between 1 and 70. The height of the image is 240 pixels.
I want to print 2 pixel thick, gray lines at relatives heights along the sequence, for the width of each basecall.
For instance, if the quality of basecall (Rectangle) 60 is 40 and its width is 20, a gray line will be drawn at 137 pixels (40 / 70 * 240) for its width.
Here is the method that draws the trace:
ChromatogramFactory chromFactory = new ChromatogramFactory();
Chromatogram chrom = ChromatogramFactory.create(abi);
ChromatogramGraphic gfx = new ChromatogramGraphic(chrom);
BufferedImage bi = new BufferedImage(
gfx.getWidth(),
gfx.getHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bi.createGraphics();
g2.setBackground(Color.white);
g2.clearRect(0, 0, bi.getWidth(), bi.getHeight());
if (g2.getClip() == null) {
g2.setClip(new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
}
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gfx.drawTo(g2);
g2.draw(new java.awt.Rectangle(-10, -10, 5, 5));
EDIT: Also, the sequence length is the same as the number of rectangles. Each letter in the sequence represents a basecall, as does each rectangle.
Using the code below, I was able to add lines at relative locations to give the confidence values:
int leftBound = 0;
int rightBound = 0;
double confVal = 0;
double heightDouble = 0;
int height = 0;
g2.setColor(Color.LIGHT_GRAY);
for (int i = 0; i < gfx.getCallboxCount(); i++) {
leftBound = (int) gfx.getCallboxBounds(i).getX();
rightBound = (int) ((int) leftBound + gfx.getCallboxBounds(i).getWidth());
confVal = confidence[i] * 2.67;
heightDouble = 200 - confVal;
height = (int) heightDouble;
g2.drawLine(leftBound, height, rightBound, height);
}
I'm trying to get an oval to change its gradient's colours every time it reaches a size of 50 or 100:
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
g2d.fillRect(0, 0, 300, 300);
if(dmt == 100 || dmt == 50) {
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
Color startColour = new Color(red, green, blue);
red = (int) (Math.random() * 256);
blue = (int) (Math.random() * 256);
green = (int) (Math.random() * 256);
Color endColour = new Color(red, green, blue);
GradientPaint gradient = new GradientPaint(300, 100, startColour, 150, 150, endColour);
g2d.setPaint(gradient);
}
g2d.fillOval((size-dmt)/2, (size-dmt)/2 - dmt/2, dmt, dmt);
}
}
(dmt is diameter, size is size of window that comes up)
I set 2 random colours for the gradients I want to use for a circle, but I want it to change only when the circle reaches either size 100 or 50 (it is constantly growing and shrinking to these sizes), but since I repaint everything white every time it runs, you can never see it except when its size is exactly 50 or 100. How do I make it always that colour, until it has to change?
How do I make it always that colour, until it has to change?
Somewhere you must have a method that changes the "dmt" variable. This method should be responsible for changing the properties of your class. So in addition to the dmt variable you should also have a startColor and endColor variables.
Then the code should be something like:
public void setDMT(...);
{
if (dmt == 50 || dmt == 100)
{
startColor = ???
endColor = ???
}
}
When you create the class you would also need to set the startColor/endColor to a default value.
Then in the paintComponent() method you simply use these two varaibles:
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
g2d.fillRect(0, 0, 300, 300);
GradientPaint gradient = new GradientPaint(300, 100, startColour, 150, 150, endColour);
g2d.setPaint(gradient);
g2d.fillOval((size-dmt)/2, (size-dmt)/2 - dmt/2, dmt, dmt);
Im looking to add colour to a greyscale image; it doesnt have to be an accurate colour representation but adding a colour to a different shade of grey, this is just to identify different areas of interest within an image. E.g. areas of vegetation are likely to be of a similar shade of grey, by adding a colour to this range of values it should be clear which areas are vegetation, which are of water, etc.
I have the code for getting colours from an image and storing them as a colour object but this doesnt seem to give a way to modify the values.
E.g. if the grey vale is less than 85, colour red, if between 86 and 170 colour green and between 171 and 255 colour blue. I have no idea how this will look but in theory the resulting image should allow a user to identify the different areas.
The current code I have for getting pixel value is below.
int total_pixels = (h * w);
Color[] colors = new Color[total_pixels];
for (int x = 0; x < w; x++)
{
for (int y = 0; y < h; y++)
{
colors[i] = new Color(image.getRGB(x, y));
i++;
}
}
for (int i = 0; i < total_pixels; i++)
{
Color c = colors[i];
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
System.out.println("Red " + r + " | Green " + g + " | Blue " + b);
}
I appreciate any help at all! Thanks a lot
You are going to have to choose your own method of converting colours from the greyscale scheme to whatever colour you want.
In the example you've given, you could do something like this.
public Color newColorFor(int pixel) {
Color c = colors[pixel];
int r = c.getRed(); // Since this is grey, the G and B values should be the same
if (r < 86) {
return new Color(r * 3, 0, 0); // return a red
} else if (r < 172) {
return new Color(0, (r - 86) * 3, 0); // return a green
} else {
return new Color(0, 0, (r - 172) * 3); // return a blue
}
}
You may have to play around a bit to get the best algorithm. I suspect that the code above will actually make your image look quite dark and dingy. You'd be better off with lighter colours. For example, you might change every 0 in the code above to 255, which will give you shades of yellow, magenta and cyan. This is going to be a lot of trial and error.
I recommend you to take a look at Java2D. It has many classes that can make your life much easier. You may end up reinventing the wheel if you ignore it.
Here is a short showcase of what you can do:
int width = 100;
int height = 100;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
image.getRGB(x, y);
Graphics2D g2d = (Graphics2D)image.getGraphics();
g2d.setColor(Color.GREEN);
g2d.fillRect(x, y, width, height);
I have two BufferedImages I loaded in from pngs. The first contains an image, the second an alpha mask for the image.
I want to create a combined image from the two, by applying the alpha mask. My google-fu fails me.
I know how to load/save the images, I just need the bit where I go from two BufferedImages to one BufferedImage with the right alpha channel.
I'm too late with this answer, but maybe it is of use for someone anyway. This is a simpler and more efficient version of Michael Myers' method:
public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
int width = image.getWidth();
int height = image.getHeight();
int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);
for (int i = 0; i < imagePixels.length; i++)
{
int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
int alpha = maskPixels[i] << 24; // Shift blue to alpha
imagePixels[i] = color | alpha;
}
image.setRGB(0, 0, width, height, imagePixels, 0, width);
}
It reads all the pixels into an array at the beginning, thus requiring only one for-loop. Also, it directly shifts the blue byte to the alpha (of the mask color), instead of first masking the red byte and then shifting it.
Like the other methods, it assumes both images have the same dimensions.
I played recently a bit with this stuff, to display an image over another one, and to fade an image to gray.
Also masking an image with a mask with transparency (my previous version of this message!).
I took my little test program and tweaked it a bit to get the wanted result.
Here are the relevant bits:
TestMask() throws IOException
{
m_images = new BufferedImage[3];
m_images[0] = ImageIO.read(new File("E:/Documents/images/map.png"));
m_images[1] = ImageIO.read(new File("E:/Documents/images/mapMask3.png"));
Image transpImg = TransformGrayToTransparency(m_images[1]);
m_images[2] = ApplyTransparency(m_images[0], transpImg);
}
private Image TransformGrayToTransparency(BufferedImage image)
{
ImageFilter filter = new RGBImageFilter()
{
public final int filterRGB(int x, int y, int rgb)
{
return (rgb << 8) & 0xFF000000;
}
};
ImageProducer ip = new FilteredImageSource(image.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(ip);
}
private BufferedImage ApplyTransparency(BufferedImage image, Image mask)
{
BufferedImage dest = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = dest.createGraphics();
g2.drawImage(image, 0, 0, null);
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.DST_IN, 1.0F);
g2.setComposite(ac);
g2.drawImage(mask, 0, 0, null);
g2.dispose();
return dest;
}
The remainder just display the images in a little Swing panel.
Note that the mask image is gray levels, black becoming full transparency, white becoming full opaque.
Although you have resolved your problem, I though I could share my take on it. It uses a slightly more Java-ish method, using standard classes to process/filter images.
Actually, my method uses a bit more memory (making an additional image) and I am not sure it is faster (measuring respective performances could be interesting), but it is slightly more abstract.
At least, you have choice! :-)
Your solution could be improved by fetching the RGB data more than one pixel at a time(see http://java.sun.com/javase/6/docs/api/java/awt/image/BufferedImage.html), and by not creating three Color objects on every iteration of the inner loop.
final int width = image.getWidth();
int[] imgData = new int[width];
int[] maskData = new int[width];
for (int y = 0; y < image.getHeight(); y++) {
// fetch a line of data from each image
image.getRGB(0, y, width, 1, imgData, 0, 1);
mask.getRGB(0, y, width, 1, maskData, 0, 1);
// apply the mask
for (int x = 0; x < width; x++) {
int color = imgData[x] & 0x00FFFFFF; // mask away any alpha present
int maskColor = (maskData[x] & 0x00FF0000) << 8; // shift red into alpha bits
color |= maskColor;
imgData[x] = color;
}
// replace the data
image.setRGB(0, y, width, 1, imgData, 0, 1);
}
For those who are using alpha in the original image.
I wrote this code in Koltin, the key point here is that if you have the alpha on your original image you need to multiply these channels.
Koltin Version:
val width = this.width
val imgData = IntArray(width)
val maskData = IntArray(width)
for(y in 0..(this.height - 1)) {
this.getRGB(0, y, width, 1, imgData, 0, 1)
mask.getRGB(0, y, width, 1, maskData, 0, 1)
for (x in 0..(this.width - 1)) {
val maskAlpha = (maskData[x] and 0x000000FF)/ 255f
val imageAlpha = ((imgData[x] shr 24) and 0x000000FF) / 255f
val rgb = imgData[x] and 0x00FFFFFF
val alpha = ((maskAlpha * imageAlpha) * 255).toInt() shl 24
imgData[x] = rgb or alpha
}
this.setRGB(0, y, width, 1, imgData, 0, 1)
}
Java version (just translated from Kotlin)
int width = image.getWidth();
int[] imgData = new int[width];
int[] maskData = new int[width];
for (int y = 0; y < image.getHeight(); y ++) {
image.getRGB(0, y, width, 1, imgData, 0, 1);
mask.getRGB(0, y, width, 1, maskData, 0, 1);
for (int x = 0; x < image.getWidth(); x ++) {
//Normalize (0 - 1)
float maskAlpha = (maskData[x] & 0x000000FF)/ 255f;
float imageAlpha = ((imgData[x] >> 24) & 0x000000FF) / 255f;
//Image without alpha channel
int rgb = imgData[x] & 0x00FFFFFF;
//Multiplied alpha
int alpha = ((int) ((maskAlpha * imageAlpha) * 255)) << 24;
//Add alpha to image
imgData[x] = rgb | alpha;
}
image.setRGB(0, y, width, 1, imgData, 0, 1);
}
Actually, I've figured it out. This is probably not a fast way of doing it, but it works:
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
Color c = new Color(image.getRGB(x, y));
Color maskC = new Color(mask.getRGB(x, y));
Color maskedColor = new Color(c.getRed(), c.getGreen(), c.getBlue(),
maskC.getRed());
resultImg.setRGB(x, y, maskedColor.getRGB());
}
}