I'm implementing a diagram that shows the level of a container. Depending on the fill level, the colour of the line should change (for instance, close to the maximum it should show red). Rather than calculating different segments of the line and setting their colours manually, I'd like to define a band in which the colour automatically changes. I thought to do this with a custom Composite/CompositeContext, but I seem not to be able to work out the locations of the pixels returned by the raster. My idea is to check for their y-Values and change the colour if a colour value is defined in the source and if the y-Value exceeds a threshold value.
My CompositeContext looks like this:
CompositeContext context = new CompositeContext() {
#Override
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
int width = Math.min(src.getWidth(), dstIn.getWidth());
int height = Math.min(src.getHeight(), dstIn.getHeight());
int[] dstPixels = new int[width];
for (int y = 0; y < height; y++) {
dstIn.getDataElements(0, y, width, 1, dstPixels);
for (int x = 0; x < width; x++) {
if ( y ??? > 50) {
dstPixels[x] = 1;
} else {
// copy pixels from src
}
}
dstOut.setDataElements(0, y, width, 1, dstPixels);
}
}
"y" seems to be related to something, but it does not contain the absolute y-Value (in fact the compose method is called several times with 32x32 rasters). Maybe someone knows how to retrieve the position on the component or even a better way to define an area in which a given pixel value is replaced by another value.
Can't you just fill with a gradient with 0 alpha and then draw the line with full alpha?
Related
I am a beginner with Java and especially Java Swing. What I am trying to do is create randomly generated "buildings" (which I have done) and on top of them add windows in rows/columns that are equally sized and spaced apart. However I want to do this in a loop (for loop?). My end goal is to also only have a few of the windows light up each time (this also being randomly generated) the code is run. Here is what I have so far, the window I have created is basically the size I want them to be. I know my attribute maxX is not used but I have created it to remind me of the max X value and in case I need it later.
import java.awt.*;
import javax.swing.*;
public class JPanelExample extends JPanel
{
private int maxX = 784;
private int maxY = 712;
#Override
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int width = (int)(Math.random()*100+100);
int height = (int)(Math.random()*350+100);
for (int i =10; i<634; i+=(width+10))
{
for (int j = 462 ; j>=462; j--)
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
g2d.setColor(Color.YELLOW);
g2d.drawRect(i+5, (maxY-height)+5, width/6, width/6);
g2d.fillRect(i+5, (maxY-height)+5, width/6, width/6);
height = (int)(Math.random()*462+100);
while (width == i+(width+10))
{
width = (int)(Math.random()*100+100);
}
}
}
}
public static void main(String[] args)
{
JFrame frame = new JFrame("Frame");
frame.add(new JPanelExample());
frame.setBackground(Color.BLACK);
frame.setSize(800, 750);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
I hope you're having fun with Java and if you're still looking for an answer (though there are many) here's my suggestion:
In order to get a row of windows on the buildings you've drawn here, you need to know:
how many windows you'll be drawing (from your code, it looks like you always want 6 windows)
where to begin drawing (from your code, this seems to be 5px after the start of each building
how much spacing between windows you want (this isn't evident in your code, but it can be whatever you want -- for this example we will use 5px again)
what colour the window should be (for this example we will use white for no-light and yellow for light turned on).
Set Up
We can store all these numbers in variables like this (using values from your code):
// for the lights:
Color off = Color.WHITE;
Color on = Color.YELLOW;
// for window calculations
int space = 5;
int startx = i+space;
int starty = maxY-height+space;
int interval = (width-space)/6;
Note that we subtract one space from the building's width before dividing by 6 to discount the initial space before the first window. I'm sure you've seen this already, but drawing diagrams can also be very helpful when starting with Java/Swing
The Loop
We can now loop through the number of windows you'd like to place in that row of windows. In this case, it looks like you'll want 6 windows every time. For each window (inside the for-loop) we first want to decide on the color (in the example we use a 10% chance of the lights being on, as noted below). Once we've set the colour, we simply use the values we've noted above to fill the next window rectangle, which is explained below. Make sure your for-loops have different variable names, since we have them nested here
for(int k = 0; k < 6; k++) {
if((int)(Math.random()*100) < 10) //10% of the time, turn the light color on
g2d.setColor(on);
else
g2d.setColor(off);
//g2d.drawRect(i+5, (maxY-height)+5, width/6, width/6);
g2d.fillRect(startx+(k*interval), starty, (interval)-space, (interval)-space);
}
As a note: the drawRect followed by fillRect here are redundant. I'm not sure if you have a specific purpose for drawing the rect first (in which case, ignore this), but view your app with the drawRect commented out to see what it does.
If you have any trouble with the Math.random function here, check out these resources:
http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html
Math.random() explained
Exploring the fillRect in our example:
fillRect takes a few variables noted below (details at: http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics.html)
fillRect(int x, int y, int width, int height)
In our example we've set the following:
x = startx + (k * interval) , k being the incremental variable for the for loop (runs from 1 .. 6), increasing the x value for the rectangle by a window's interval after each window drawn.
y = starty , this is consistent for each row.
width, height = interval - space , this sets the dimensions of the window and ensures a proper space before the next window.
This should give you a working 1 row of windows.
Columns
Getting columns of windows is very similar to getting the row. There are many ways to do all of these things, but I'll briefly go through another nested-for-loop.
In order to get columns of windows we need to know:
How many rows you want to have (how many windows per column)
Because we've already set up window dimensions we can determine the number of windows that can fit in a column using the following:
int num_rows = (height - space) / interval;
//and to set a maximum number of rows to a column (ex: 6)
int num_rows = Math.min(((height-space) / interval), 6); //a max number can replace 6 here
Here we'll use another for-loop to continuously add rows, num_rows times. Note: we've changed fillRect such that y = starty + (l*interval) , incrementing the start position y to the next location of rows each time one row is drawn.
for(int l = 0; l < num_rows; l++){
for(int k = 0; k < 6; k++) {
if((int)(Math.random()*100) < 10) //10% of the time, turn the light color on
g2d.setColor(on);
else
g2d.setColor(off);
g2d.fillRect(startx+(k*interval), starty + (l*interval), (interval)-space, (interval)-space);
}
}
And that's that. Hope that's helpful and not too confusing.
After a long time, I figured out a solution. I have changed the for-loop to this:
for (int i =10; i<634; i+=(a+10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
rows = Math.round((height)/25);
columns = Math.round(width/25);
for (int j = 1; j<=columns; j++)//windows
{
for (int k = 1; k<=rows; k++)
{
g2d.setColor(Color.BLACK);
g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
if (Math.random()<0.7)
{
g2d.setColor(Color.YELLOW);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
else
{
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int)(Math.random()*462+100);
width = (int)(Math.random()*100+100);
}
I am using the Java HeatMap library (http://www.mbeckler.org/heatMap/) to generate heatMap for my data.
I am working with a daatset which has NA values. So, basically I want no color (white color) for the pixels which have NA value. But, unfortunately this library does not support data with NA value and what I get is a plan block of image with the base color. I tried looking into the source code, so as to make some changes. Within the code, the drawData() method is used to color each pixel in the bufferedImage (possibly!). Can someone help me with how I can implement support for NA values and to show them with no color? I have little to no experience with BufferedImage and Graphics2D class.
Here is the drawData() method from the source code of the library:
/**
* Creates a BufferedImage of the actual data plot.
*
* After doing some profiling, it was discovered that 90% of the drawing
* time was spend drawing the actual data (not on the axes or tick marks).
* Since the Graphics2D has a drawImage method that can do scaling, we are
* using that instead of scaling it ourselves. We only need to draw the
* data into the bufferedImage on startup, or if the data or gradient
* changes. This saves us an enormous amount of time. Thanks to
* Josh Hayes-Sheen (grey#grevian.org) for the suggestion and initial code
* to use the BufferedImage technique.
*
* Since the scaling of the data plot will be handled by the drawImage in
* paintComponent, we take the easy way out and draw our bufferedImage with
* 1 pixel per data point. Too bad there isn't a setPixel method in the
* Graphics2D class, it seems a bit silly to fill a rectangle just to set a
* single pixel...
*
* This function should be called whenever the data or the gradient changes.
*/
private void drawData()
{
// System.out.println("Column: " + data.length + " row: " + data[0].length);
bufferedImage = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_ARGB);
bufferedGraphics = bufferedImage.createGraphics();
for (int x = 0; x < data.length; x++)
{
for (int y = 0; y < data[0].length; y++)
{
bufferedGraphics.setColor(colors[dataColorIndices[x][y]]);
bufferedGraphics.fillRect(x, y, 1, 1);
}
}
}
sample data can be found at: http://www.filedropper.com/data_13
So, this is how it looks at the moment:
From Java:
From R:
Please ignore the orientation of the two images
As the BufferedImage is created as ARGB, you could just not paint anything on those pixels that should be undefined, and they'll stay transparent. Something like:
public static int NA = ...;
private void drawData()
{
bufferedImage = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_ARGB);
Graphics2D bufferedGraphics = bufferedImage.createGraphics();
try {
for (int x = 0; x < data.length; x++)
{
for (int y = 0; y < data[0].length; y++)
{
int colorIndex = dataColorIndices[x][y];
if (colorIndex != NA) {
bufferedGraphics.setColor(colors[colorIndex]);
bufferedGraphics.fillRect(x, y, 1, 1);
}
// Alternate flow, if you really want the pixels to be white
// else {
// bufferedGraphics.setColor(Color.WHITE);
// bufferedGraphics.fillRect(x, y, 1, 1);
// }
}
}
}
finally {
bufferedGraphics.dispose();
}
}
I also dispose the Graphics2D instance, to avoid resource leaks.
We are trying to get only the portion of the image out of the captured image. But in java we only get subimage in rectangular form using image.getImage(x,y,width, height). Let say if i virutally split the image as 10 parts as shown below. How can i able to extract only 1,2,4,6,8,9,10 out of it as show in the second image using native java very without consuming too many resources and time.
Update
Below is the sample code
for (int x = 0; x < columns; x++) {
for (int y = 0; y < rows; y++) {
imagePart = img.getSubimage(x * this.smallWidth, y
* this.smallHeight, this.smallWidth,
this.smallHeight);
if (!ifSelectedPart(imagePart)) {
smallImages[x][y] = imagePart;
}
else {
smallImages[x][y] = fillwithAlpha();
}
}
createImage(smallImages[][])
If these rectangles are all the same size you can treat the image as a grid and calculate what region of the image you need with a little math.
int numberColumns = 2;
int numberRows = 5;
public Rectangle getSubregion(int row, int column, int imgWidth, int imgHeight){
int cellWidth = imgWidth / numberColumns;
int cellHeight = imgHeight / numberRows;
return new Rectangle(column*cellWidth, row*cellHeight,cellWidth, cellHeight);
}
//usage
Rectangle cellOne = getSubregion(0, 0, img.getWidth(),img.getHeight());
Then just render each of those subregions to a new image in memory.
Images are by their nature rectangular. Perhaps you wish to draw over the image with 0 alpha composite color to cover up the region that you don't want to see. Either that or create a grid of rectangular sub-images, and keep the ones from the grid that you want to display.
Using some math, i created the following java-function, to input a Bitmap, and have it crop out a centered square in which a circle is cropped out again with a black border around it.
The rest of the square should be transparent.
Additionatly, there is a transparent distance to the sides to not damage the preview when sending the image via Messengers.
The code of my function is as following:
public static Bitmap edit_image(Bitmap src,boolean makeborder) {
int width = src.getWidth();
int height = src.getHeight();
int A, R, G, B;
int pixel;
int middlex = width/2;
int middley = height/2;
int seitenlaenge,startx,starty;
if(width>height)
{
seitenlaenge=height;
starty=0;
startx = middlex - (seitenlaenge/2);
}
else
{
seitenlaenge=width;
startx=0;
starty = middley - (seitenlaenge/2);
}
int kreisradius = seitenlaenge/2;
int mittx = startx + kreisradius;
int mitty = starty + kreisradius;
int border=2;
int seitenabstand=55;
Bitmap bmOut = Bitmap.createBitmap(seitenlaenge+seitenabstand, seitenlaenge+seitenabstand, Bitmap.Config.ARGB_8888);
bmOut.setHasAlpha(true);
for(int x = 0; x < width; ++x) {
for(int y = 0; y < height; ++y) {
int distzumitte = (int) (Math.pow(mittx-x,2) + Math.pow(mitty-y,2)); // (Xm-Xp)^2 + (Ym-Yp)^2 = dist^2
distzumitte = (int) Math.sqrt(distzumitte);
pixel = src.getPixel(x, y);
A = Color.alpha(pixel);
R = (int)Color.red(pixel);
G = (int)Color.green(pixel);
B = (int)Color.blue(pixel);
int color = Color.argb(A, R, G, B);
int afterx=x-startx+(seitenabstand/2);
int aftery=y-starty+(seitenabstand/2);
if(x < startx || y < starty || afterx>=seitenlaenge+seitenabstand || aftery>=seitenlaenge+seitenabstand) //seitenrand
{
continue;
}
else if(distzumitte > kreisradius)
{
color=0x00FFFFFF;
}
else if(distzumitte > kreisradius-border && makeborder) //border
{
color = Color.argb(A, 0, 0, 0);
}
bmOut.setPixel(afterx, aftery, color);
}
}
return bmOut;
}
This function works fine, but there are some problems occuring that i wasn't able to resolve yet.
The quality of the image is decreased significantly
The border is not really round, but appears to be flat at the edges of the image (on some devices?!)
I'd appreciate any help regarding that problems. I got to admit that i'm not the best in math and there should probably be a better formula to ceate the border.
your source code is hard to read, since it is a mix of German and English in the variable names. Additionally you don't say which image library you use, so we don't exactly know where the classes Bitmap and Color come from.
Anyway, it is very obvious, that you are operating only on a Bitmap. Bitmap means the whole image is stored in the RAM pixel by pixel. There is no lossy compression. I don't see anything in your source code, that can affect the quality of the image.
It is very likely, that the answer is in the Code that you don't show us. Additionally, what you describe (botrh of the problems) sounds like a very typical low quality JPEG compression. I am sure, somewhere after you call you function, you convert/save the image to a JPEG. Try to do that at that position to BMP, TIFF or PNG and see that the error disappears magically. Maybe you can also set the quality level of the JPEG somewhere to avoid that.
To make it easier for others (maybe) also to find a good answer, please allow me to translate your code to English:
public static Bitmap edit_image(Bitmap src,boolean makeborder) {
int width = src.getWidth();
int height = src.getHeight();
int A, R, G, B;
int pixel;
int middlex = width/2;
int middley = height/2;
int sideLength,startx,starty;
if(width>height)
{
sideLength=height;
starty=0;
startx = middlex - (sideLength/2);
}
else
{
sideLength=width;
startx=0;
starty = middley - (sideLength/2);
}
int circleRadius = sideLength/2;
int middleX = startx + circleRadius;
int middleY = starty + circleRadius;
int border=2;
int sideDistance=55;
Bitmap bmOut = Bitmap.createBitmap(sideLength+sideDistance, sideLength+sideDistance, Bitmap.Config.ARGB_8888);
bmOut.setHasAlpha(true);
for(int x = 0; x < width; ++x) {
for(int y = 0; y < height; ++y) {
int distanceToMiddle = (int) (Math.pow(middleX-x,2) + Math.pow(middleY-y,2)); // (Xm-Xp)^2 + (Ym-Yp)^2 = dist^2
distanceToMiddle = (int) Math.sqrt(distanceToMiddle);
pixel = src.getPixel(x, y);
A = Color.alpha(pixel);
R = (int)Color.red(pixel);
G = (int)Color.green(pixel);
B = (int)Color.blue(pixel);
int color = Color.argb(A, R, G, B);
int afterx=x-startx+(sideDistance/2);
int aftery=y-starty+(sideDistance/2);
if(x < startx || y < starty || afterx>=sideLength+sideDistance || aftery>=sideLength+sideDistance) //margin
{
continue;
}
else if(distanceToMiddle > circleRadius)
{
color=0x00FFFFFF;
}
else if(distanceToMiddle > circleRadius-border && makeborder) //border
{
color = Color.argb(A, 0, 0, 0);
}
bmOut.setPixel(afterx, aftery, color);
}
}
return bmOut;
}
I think that you need to check PorterDuffXferMode.
You will find some technical informations about compositing images modes HERE.
There is some good example of making bitmap with rounded edges HERE. You just need to tweak a bit source code and you're ready to go...
Hope it will help.
Regarding the quality I can't see anything wrong with your method. Running the code with Java Swing no quality is lost. The only problem is that the image has aliased edges.
The aliasing problem will tend to disappear as the screen resolution increases and would be more noticeable for lower resolutions. This might explain why you see it in some devices only.The same problem applies to your border but in that case it would be more noticable since the color is single black.
Your algorithm defines a square area of the original image. To find the square it starts from the image's center and expand to either the width or the height of the image whichever is smaller. I am referring to this area as the square.
The aliasing is caused by your code that sets the colors (I am using pseudo-code):
if ( outOfSquare() ) {
continue; // case 1: this works but you depend upon the new image' s default pixel value i.e. transparent black
} else if ( insideSquare() && ! insideCircle() ) {
color = 0x00FFFFFF; // case 2: transparent white. <- Redundant
} else if ( insideBorder() ) {
color = Color.argb(A, 0, 0, 0); // case 3: Black color using the transparency of the original image.
} else { // inside the inner circle
// case 4: leave image color
}
Some notes about the code:
Case 1 depends upon the default pixel value of the original image i.e. transparent black. It works but better to set it explicitly
Case 2 is redundant. Handle it in the same way you handle case 1. We are only interested in what happens inside the circle.
Case 3 (when you draw the border) is not clear what it expects. Using the alpha of the original image has the potential of messing up your new image if it happens that the original alpha varies along the circle's edges. So this is clearly wrong and depending on the image, can potentially be another cause of your problems.
Case 4 is ok.
Now at your circle's periphery the following color transitions take place:
If border is not used: full transparency -> full image color (case 2 and 4 in the pseudocode)
If border is used: full transparency -> full black -> full image color (cases 2, 3 and 4)
To achieve a better quality at the edges you need to introduce some intermediate states that would make the transitions smoother (the new transitions are shown in italics):
Border is not used: full transparency -> partial transparency with image color -> full image color
Border is used: full transparency -> partial transparency of Black color -> full Black color -> partial transparency of Black color + Image color (i.e. blending) -> Full image color
I hope that helps
I am working with 24x24 pixel icons. And I would like to be able to change a specific color within this icon to a different color. For example turn the white areas to red.
I don't know of an API method that does that. And by default, Images are not writable. However, if you have a BufferedImage, you could do it like this:
public void changeColor(BufferedImage img, Color old, Color new) {
final int oldRGB = old.getRGB();
final int newRGB = new.getRGB();
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
if (img.getRGB(x, y) == oldRGB)
img.setRGB(x, y, newRGB);
}
}
}
This is not the most efficient way to do it (it's possible to fetch RGB data into an array instead of one pixel at a time), but for 24x24 images it shouldn't be a problem.
You can do this with a BufferedImage. Take a look at the Java Image I/O documentation.