Rounding up to the nearest 4 (simple math) - java

I am REALLY bad at math and have been trying to figure this out for quite a while but its all guesswork really.
I have a grid that is filled with X boxes. 4 boxes can fit horizontally along the grid. For every multiple of 4 boxes +1, the grid needs to expand vertically to fit a new row of boxes in.
Each box is 300 units deep/in height.
So say I have 4 boxes, the grid needs to be 300 units deep.
If I have 5 boxes it needs to be 600 units deep.
If I have 8 boxes it needs to be 600 units deep.
If I have 9 boxes it needs to be 900 units deep.
If I have 14 boxes it needs to be 1200 units deep.
Here is my mostly commented code trying to figure this out by Googling for people's solutions to rounding up.
The closest I am at the moment is:
height of grid = numberofentries rounded up to the nearest 4, divided by 4, times 300px
Thankyou for reading. I am shit at math. Below is my random commented stuff and someones function that may or may not be needed (there are plenty of math functions on Google but I just don't know what I'm doing)
//Integer totalHeight= 300*((Math.round(imageURLs.size()/6)));
//Integer totalHeight = imageURLs.size()*300/4;
Integer totalHeight = (roundUp(imageURLs.size(), 4)) / 4 * 300;
// height = numberofentries / 4 rounded up to the nearest multiple of 4
// height = numberofentries rounded up to the nearest 4, divided by 4, times 300px
//Double heightMath= 300*(4*(Math.ceil(Math.abs(imageURLs.size()/4))));
//Long heightMath= 300*(long)Math.floor(imageURLs.size() + 1d);
//Integer totalHeight = (int) (double) heightMath;
int roundUp (int numToRound, int multiple) {
if (multiple == 0) {
return numToRound;
}
int remainder = numToRound % multiple;
if (remainder == 0) {
return numToRound;
}
return numToRound + multiple - remainder;
}
I don't even know if I'm supposed to be "rounding". It could be some other math term... PS this is not homework, its for functionality in my project that composites images to a single image ...just they need formatting
Here is the full code and the output image from the http://imgur.com/39BgxkL (warning 2mb image)
public class testImage {
//int roundUp(int numToRound, int multiple)
//{
// if(multiple == 0)
// {
// return numToRound;
// }
//
// int remainder = numToRound % multiple;
// if (remainder == 0)
// return numToRound;
// return numToRound + multiple - remainder;
//}
int roundUp(int numToRound, int multiple) {
return (numToRound+multiple-1) / multiple;
}
public testImage() throws IOException
{
ArrayList <String> imageURLs = new ArrayList<>();
imageURLs.add("C:\\Users\\J\\Desktop\\test1.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test2.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test3.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test4.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test5.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test6.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test7.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test1.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test2.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test3.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test4.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test5.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test6.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test7.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test7.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test6.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test7.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test1.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test2.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test3.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test4.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test5.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test6.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test7.jpg");
//Integer totalHeight= 300*((Math.round(imageURLs.size()/6)));
//Integer totalHeight = imageURLs.size()*300/4;
//Integer totalHeight = 300*(roundUp(imageURLs.size(), 4));
//Integer totalHeight = (roundUp(imageURLs.size(),4))/4*300;
// height = numberofentries / 4 rounded up to the nearest multiple of 4
// height = numberofentries rounded up to the nearest 4, divided by 4, times 300px
Double heightMath= 300*(4*(Math.ceil(Math.abs(imageURLs.size()/4.0))));
//Long heightMath= 300*(long)Math.floor(imageURLs.size() + 1d);
Integer totalHeight = (int) (double) heightMath;
if (totalHeight < 300){
totalHeight = 300;
}
BufferedImage result = new BufferedImage(
864, totalHeight, //work these out
BufferedImage.TYPE_INT_RGB);
Graphics g = result.getGraphics();
Integer x = 0;
Integer y = 0;
for(String imageURL : imageURLs){
BufferedImage bi = ImageIO.read(new File(imageURL));
g.drawImage(bi, x, y, null);
x += 216;
if(x > result.getWidth()){
x = 0;
y += bi.getHeight();
}
ImageIO.write(result,"png",new File("C:\\Users\\J\\Desktop\\resultimage.jpg"));
}

The common trick to round the division by N is to add N-1, and divide in integers:
int roundUp(int numToRound, int multiple) {
return (numToRound+multiple-1) / multiple;
}
This assumes that multiple is not zero.

Sorry to put you through all this. I discovered that the dimensions of the test images I was using is actually 200 instead of the 300 that I had stretched them to on my html pages. This caused the big gap in the image composite and made me think the formulas weren't working.
I also found that I had to change values in the rest of the code to compensate for this, as images were missing off the screen.
Thanks for your help everyone.
A screenshot shows it working: http://i.imgur.com/vAiakfQ.jpg
The working code is:
public class testImage {
int roundUp(int numToRound, int multiple) {
return (numToRound+multiple-1) / multiple;
}
public testImage() throws IOException
{
ArrayList <String> imageURLs = new ArrayList<>();
imageURLs.add("C:\\Users\\J\\Desktop\\test5.jpg");
imageURLs.add("C:\\Users\\J\\Desktop\\test5.jpg");
Integer totalHeight = (roundUp(imageURLs.size(),4))*200;
System.out.println(imageURLs.size());
System.out.println(totalHeight);
BufferedImage result = new BufferedImage(
736, totalHeight, //work these out
BufferedImage.TYPE_INT_RGB);
Graphics g = result.getGraphics();
Integer x = 0;
Integer y = 0;
for(String imageURL : imageURLs){
BufferedImage bi = ImageIO.read(new File(imageURL));
g.drawImage(bi, x, y, null);
x += 184;
if(x >= result.getWidth()){
x = 0;
y += bi.getHeight();
}
ImageIO.write(result,"png",new File("C:\\Users\\J\\Desktop\\resultimage.jpg"));
}
}
}

Related

Is there any way to detect image is Blurry after capturing from Camera in Android (Except OpenCV )

I am working with Camera2 API and want to detect captured image is blurry or clear, i used OpenCV for this but result is not satisfactory and it increases APK size 3 times, So is there any way to detect blurry?
Measuring image focus/blur involves iterating of the pixels of the bitmap, or at least a portion thereof.
While you don't need OpenCV to iterate over the pixels of a bitmap on Android, its not for the faint of heart. Doing so in a performant way would require you to drop into JNI native code, or perhaps a technology like RenderScript, as iterating over pixels in Java or Kotlin might prove too slow.
There are many algorithms and techniques for measuring focus, or sharpness, or contrast, this is one I've used with reasonable success.
Luma is the luminosity of a pixel, i.e. grayscale pixel value. You'll want to convert each pixel to a grayscale value for this focus measure. e.g. using the NTSC formula:
pixelLuma = (red * 0.299) + (green * 0.587) + (blue * 0.114)
Here is a suggested formula to measure focus score:
FocusScore = Max({Video_Gradient}) / {Gray_Level_Dynamic_Range} * {Pixel_Pitch}
Max{Video_Gradient} = a measure of the maximum luminosity difference between adjacent pixels (x,y) across the bitmap.
e.g.:
horizontally measure pixel[x] - pixel[x+1]
vertically measure pixel[y] - pixel[y+1]
{Gray_Level_Dynamic_Range} = difference between average of N lightest pixels and N darkest pixels across the bitmap. A typical value for N is 64, in my case working on images around 1200w x 500h. Smaller images should use smaller N.
{Pixel_Pitch} = 1 / DPI = 1/200 = 0.005
This will result in a score, higher values are more in focus. You can determine a reasonable threshold.
Here is a code snippet written in C:
width = width of bitmap
height = height of bitmap
pixels = an array of bytes of size (width * height) holding pixel luminosity values
VFOCUS_N = 64
int gradientHorizontal[256];
int *pGradientHorizontal = gradientHorizontal;
int gradientVertical[256];
int *pGradientVertical = gradientVertical;
int luminanceHistogram[256];
int *pLuminance = luminanceHistogram;
int maxGradient = 0;
for (int i = 0;i < 256;i++)
{
gradientHorizontal[i] = 0;
gradientVertical[i] = 0;
luminanceHistogram[i] = 0;
}
// pixel by pixel math...
for (nRow = 0; nRow < height-1; nRow++)
{
nRowOffset = nRow * width;
nNextRowOffset = (nRow+1) * width;
for (nCol = 0; nCol < width-1; nCol++)
{
int gC = pixels[nRowOffset + nCol];
int gH = abs(gC - pixels[nRowOffset + nCol + 1]);
int gV = abs(gC - pixels[nNextRowOffset + nCol]);
pLuminance[gC]++;
pGradientHorizontal[gH]++;
pGradientVertical[gV]++;
}
}
// find max gradient
for (int i = 255;i >= 0;i--)
{
// first one with a value
if ((gradientHorizontal[i] > 0) || (gradientVertical[i] > 0))
{
maxGradient = i;
break;
}
}
// calculate dynamic range
int rangeLow = 0;
int rangeHi = 0;
int p;
p = 0;
for (int i = 0;i < 256;i++)
{
if (luminanceHistogram[i] > 0)
{
if (p + luminanceHistogram[i] > VFOCUS_N)
{
rangeLow += (i * (VFOCUS_N - p));
p = VFOCUS_N;
break;
}
p += luminanceHistogram[i];
rangeLow += (i * luminanceHistogram[i]);
}
}
if (p)
rangeLow /= p;
p = 0;
for (int i = 255;i >= 0;i--)
{
if (luminanceHistogram[i] > 0)
{
if (p + luminanceHistogram[i] > VFOCUS_N)
{
rangeHi += (i * (VFOCUS_N - p));
p = VFOCUS_N;
break;
}
p += luminanceHistogram[i];
rangeHi += (i * luminanceHistogram[i]);
}
}
if (p)
rangeHi /= p;
float mFocusScore = (float)fmin((float)maxGradient / (fabs((float)rangeHi - (float)rangeLow) * 0.005), 100.00);
Low focus scores means a blurry image. Values close to or in excess of 100 indicate a sharp image, the code above caps the score at 100.

Algorithm to find neighboring subimages

I have an image and I am extracting a subimage to feed to my neural network. I am attempting to calculate the average output of all the subimages in that same neighborhood.
So if I have an original image (m X n pixels) and I found a subimage at (sub_x, sub_y) with size (sub_width and sub_height), I also need to extract subimages with the same size (sub_width and sub_height) at (sub_x + m, sub_y + n) where m and n go from 1 - 3.
I already have a working solution:
for (int subX = (x-3); subX < (x+4); subX++)
for (int subY = (y-3); subY < (y+4); subY++)
if ( (subX > 0) && (subY > 0) )
if ( ((subX + width) < img.getWidth()) && ((subY + height) < img.getHeight()) ){
counter++;
testingImage = img.getSubimage(subX, subY, width, height);
}
x,y, width, and height are all integers of the original subimage I found.
img is the original full sized BufferedImage.
I'm not too happy about the performance though. Is there a faster/smarter way to do this?
Here's one simple thing you can do: get rid of those conditions inside loops. Calculate your ranges first, just once and then run the loops without range checks.
int subXStart = max(x-3, 0);
int subYStart = max(y-3, 0);
int subXEnd = min(x+4, img.getWidth() - width);
int subYEnd = min(y+4, img.getHeight() - height);
for (int subX = subXStart; subX < subXEnd; subX++) {
for (int subY = subYStart; subY < subYEnd; subY++) {
counter++;
testingImage = img.getSubimage(subX, subY, width, height);
// run your neural net
}
}
You can also try switching the order of loops. The order that matches the memory ordering should be considerably faster.

Color quantization with N out of M predefined colors

I am having a slightly odd problem trying to quantize and dither an RGB image. Ideally, I should be able to implement a suitable algorithm in Java or use a Java library, but references to implementations in other languages may be helpful as well.
The following is given as input:
image: 24-bit RGB bitmap
palette: a list of colors defined with their RGB values
max_cols: the maximum number of colours to be used in the output image
It is perhaps important, that both the size of the palette as well as the maximum number of allowed colours is not necessarily a power of 2 and may be greater than 255.
So, the goal is to take the image, select up to max_cols colours from the provided palette and output an image using only the picked colours and rendered using some kind of error-diffusion dithering. Which dithering algorithm to use is not that important, but it should be an error-diffusion variant (e.g. Floyd-Steinberg) and not simple halftone or ordered dithering.
Performance is not particularly important and the size of the expected data input is relatively small. The images would rarely be larger than 500x500 pixel, the provided palette may contain some 3-400 colours and the number of colours will usually be limited to less than 100. It is also safe to assume that the palette contains a wide selection of colours, covering variations of both hue, saturation and brightness.
The palette selection and dithering used by scolorq would be ideal, but it does not seem easy to adapt the algorithm to select colours from an already defined palette instead of arbitrary colours.
To be more precise, the problem where I am stuck is the selection of suitable colours from the provided palette. Assume that I e.g. use scolorq to create a palette with N colours and later replace the colours defined by scolorq with the closest colours from the provided palette, and then use these colours combined with error-diffused dithering. This will produce a result at least similar to the input image, but due to the unpredictable hues of the selected colours, the output image may get a strong, undesired colour cast. E.g. when using a grey-scale input image and a palette with only few neutral gray tones, but a great range of brown tones (or more generally, many colours with the same hue, low saturation and a great variation in the brightness), my colour selection algorithm seem to prefer these colours above the neutral greys since the brown tones are at least mathematically closer to the desired colour than the greys. The same problem remains even if I convert the RGB values to HSB and use different weights for the H, S and B channels when trying to find the nearest available colour.
Any suggestions how to implement this properly, or even better a library I can use to perform the task?
Since Xabster asked, I can also explain the goal with this excercise, although it has nothing to do with how the actual problem can be solved. The target for the output image is an embroidery or tapestry pattern. In the most simplest case, each pixel in the output image corresponds to a stitch made on some kind of carrier fabric. The palette corresponds to the available yarns, which usually come in several hundred colours. For practical reasons, it is however necessary to limit the number of colours used in the actual work. Googling for gobelin embroideries will give several examples.
And to clarify where the problem exactly lies... The solution can indeed be split into two separate steps:
selecting the optimal subset of the original palette
using the subset to render the output image
Here, the first step is the actual problem. If the palette selection works properly, I could simply use the selected colours and e.g. Floyd-Steinberg dithering to produce a reasonable result (which is rather trivial to implement).
If I understand the implementation of scolorq correctly, scolorq however combines these two steps, using knowledge of the dithering algorithm in the palette selection to create an even better result. That would of course be a preferred solution, but the algorithms used in scolorq work slightly beyond my mathematical knowledge.
OVERVIEW
This is a possible approach to the problem:
1) Each color from the input pixels is mapped to the closest color from the input color palette.
2) If the resulting palette is greater than the allowed maximum number of colors, the palette gets reduced to the maximum allowed number, by removing the colors, that are most similar with each other from the computed palette (I did choose the nearest distance for removal, so the resulting image remains high in contrast).
3) If the resulting palette is smaller than the allowed maximum number of colors, it gets filled with the most similar colors from the remaining colors of the input palette until the allowed number of colors is reached. This is done in the hope, that the dithering algorithm could make use of these colors during dithering. Note though that I didn't see much difference between filling or not filling the palette for the Floyd-Steinberg algorithm...
4) As a last step the input pixels get dithered with the computed palette.
IMPLEMENTATION
Below is an implementation of this approach.
If you want to run the source code, you will need this class: ImageFrame.java. You can set the input image as the only program argument, all other parameters must be set in the main method. The used Floyd-Steinberg algorithm is from Floyd-Steinberg dithering.
One can choose between 3 different reduction strategies for the palette reduction algorithm:
1) ORIGINAL_COLORS: This algorithm tries to stay as true to the input pixel colors as possible by searching for the two colors in the palette, that have the least distance. From these two colors it removes the one with the fewest mappings to pixels in the input map.
2) BETTER_CONTRAST: Works like ORIGINAL_COLORS, with the difference, that from the two colors it removes the one with the lowest average distance to the rest of the palette.
3) AVERAGE_DISTANCE: This algorithm always removes the colors with the lowest average distance from the pool. This setting can especially improve the quality of the resulting image for grayscale palettes.
Here is the complete code:
import java.awt.Color;
import java.awt.Image;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public class Quantize {
public static class RGBTriple {
public final int[] channels;
public RGBTriple() { channels = new int[3]; }
public RGBTriple(int color) {
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = (color >> 0) & 0xFF;
channels = new int[]{(int)r, (int)g, (int)b};
}
public RGBTriple(int R, int G, int B)
{ channels = new int[]{(int)R, (int)G, (int)B}; }
}
/* The authors of this work have released all rights to it and placed it
in the public domain under the Creative Commons CC0 1.0 waiver
(http://creativecommons.org/publicdomain/zero/1.0/).
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Retrieved from: http://en.literateprograms.org/Floyd-Steinberg_dithering_(Java)?oldid=12476
*/
public static class FloydSteinbergDither
{
private static int plus_truncate_uchar(int a, int b) {
if ((a & 0xff) + b < 0)
return 0;
else if ((a & 0xff) + b > 255)
return (int)255;
else
return (int)(a + b);
}
private static int findNearestColor(RGBTriple color, RGBTriple[] palette) {
int minDistanceSquared = 255*255 + 255*255 + 255*255 + 1;
int bestIndex = 0;
for (int i = 0; i < palette.length; i++) {
int Rdiff = (color.channels[0] & 0xff) - (palette[i].channels[0] & 0xff);
int Gdiff = (color.channels[1] & 0xff) - (palette[i].channels[1] & 0xff);
int Bdiff = (color.channels[2] & 0xff) - (palette[i].channels[2] & 0xff);
int distanceSquared = Rdiff*Rdiff + Gdiff*Gdiff + Bdiff*Bdiff;
if (distanceSquared < minDistanceSquared) {
minDistanceSquared = distanceSquared;
bestIndex = i;
}
}
return bestIndex;
}
public static int[][] floydSteinbergDither(RGBTriple[][] image, RGBTriple[] palette)
{
int[][] result = new int[image.length][image[0].length];
for (int y = 0; y < image.length; y++) {
for (int x = 0; x < image[y].length; x++) {
RGBTriple currentPixel = image[y][x];
int index = findNearestColor(currentPixel, palette);
result[y][x] = index;
for (int i = 0; i < 3; i++)
{
int error = (currentPixel.channels[i] & 0xff) - (palette[index].channels[i] & 0xff);
if (x + 1 < image[0].length) {
image[y+0][x+1].channels[i] =
plus_truncate_uchar(image[y+0][x+1].channels[i], (error*7) >> 4);
}
if (y + 1 < image.length) {
if (x - 1 > 0) {
image[y+1][x-1].channels[i] =
plus_truncate_uchar(image[y+1][x-1].channels[i], (error*3) >> 4);
}
image[y+1][x+0].channels[i] =
plus_truncate_uchar(image[y+1][x+0].channels[i], (error*5) >> 4);
if (x + 1 < image[0].length) {
image[y+1][x+1].channels[i] =
plus_truncate_uchar(image[y+1][x+1].channels[i], (error*1) >> 4);
}
}
}
}
}
return result;
}
public static void generateDither(int[] pixels, int[] p, int w, int h){
RGBTriple[] palette = new RGBTriple[p.length];
for (int i = 0; i < palette.length; i++) {
int color = p[i];
palette[i] = new RGBTriple(color);
}
RGBTriple[][] image = new RGBTriple[w][h];
for (int x = w; x-- > 0; ) {
for (int y = h; y-- > 0; ) {
int index = y * w + x;
int color = pixels[index];
image[x][y] = new RGBTriple(color);
}
}
int[][] result = floydSteinbergDither(image, palette);
convert(result, pixels, p, w, h);
}
public static void convert(int[][] result, int[] pixels, int[] p, int w, int h){
for (int x = w; x-- > 0; ) {
for (int y = h; y-- > 0; ) {
int index = y * w + x;
int index2 = result[x][y];
pixels[index] = p[index2];
}
}
}
}
private static class PaletteColor{
final int color;
public PaletteColor(int color) {
super();
this.color = color;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + color;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PaletteColor other = (PaletteColor) obj;
if (color != other.color)
return false;
return true;
}
public List<Integer> indices = new ArrayList<>();
}
public static int[] getPixels(Image image) throws IOException {
int w = image.getWidth(null);
int h = image.getHeight(null);
int pix[] = new int[w * h];
PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, pix, 0, w);
try {
if (grabber.grabPixels() != true) {
throw new IOException("Grabber returned false: " +
grabber.status());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return pix;
}
/**
* Returns the color distance between color1 and color2
*/
public static float getPixelDistance(PaletteColor color1, PaletteColor color2){
int c1 = color1.color;
int r1 = (c1 >> 16) & 0xFF;
int g1 = (c1 >> 8) & 0xFF;
int b1 = (c1 >> 0) & 0xFF;
int c2 = color2.color;
int r2 = (c2 >> 16) & 0xFF;
int g2 = (c2 >> 8) & 0xFF;
int b2 = (c2 >> 0) & 0xFF;
return (float) getPixelDistance(r1, g1, b1, r2, g2, b2);
}
public static double getPixelDistance(int r1, int g1, int b1, int r2, int g2, int b2){
return Math.sqrt(Math.pow(r2 - r1, 2) + Math.pow(g2 - g1, 2) + Math.pow(b2 - b1, 2));
}
/**
* Fills the given fillColors palette with the nearest colors from the given colors palette until
* it has the given max_cols size.
*/
public static void fillPalette(List<PaletteColor> fillColors, List<PaletteColor> colors, int max_cols){
while (fillColors.size() < max_cols) {
int index = -1;
float minDistance = -1;
for (int i = 0; i < fillColors.size(); i++) {
PaletteColor color1 = colors.get(i);
for (int j = 0; j < colors.size(); j++) {
PaletteColor color2 = colors.get(j);
if (color1 == color2) {
continue;
}
float distance = getPixelDistance(color1, color2);
if (index == -1 || distance < minDistance) {
index = j;
minDistance = distance;
}
}
}
PaletteColor color = colors.get(index);
fillColors.add(color);
}
}
public static void reducePaletteByAverageDistance(List<PaletteColor> colors, int max_cols, ReductionStrategy reductionStrategy){
while (colors.size() > max_cols) {
int index = -1;
float minDistance = -1;
for (int i = 0; i < colors.size(); i++) {
PaletteColor color1 = colors.get(i);
float averageDistance = 0;
int count = 0;
for (int j = 0; j < colors.size(); j++) {
PaletteColor color2 = colors.get(j);
if (color1 == color2) {
continue;
}
averageDistance += getPixelDistance(color1, color2);
count++;
}
averageDistance/=count;
if (minDistance == -1 || averageDistance < minDistance) {
minDistance = averageDistance;
index = i;
}
}
PaletteColor removed = colors.remove(index);
// find the color with the least distance:
PaletteColor best = null;
minDistance = -1;
for (int i = 0; i < colors.size(); i++) {
PaletteColor c = colors.get(i);
float distance = getPixelDistance(c, removed);
if (best == null || distance < minDistance) {
best = c;
minDistance = distance;
}
}
best.indices.addAll(removed.indices);
}
}
/**
* Reduces the given color palette until it has the given max_cols size.
* The colors that are closest in distance to other colors in the palette
* get removed first.
*/
public static void reducePalette(List<PaletteColor> colors, int max_cols, ReductionStrategy reductionStrategy){
if (reductionStrategy == ReductionStrategy.AVERAGE_DISTANCE) {
reducePaletteByAverageDistance(colors, max_cols, reductionStrategy);
return;
}
while (colors.size() > max_cols) {
int index1 = -1;
int index2 = -1;
float minDistance = -1;
for (int i = 0; i < colors.size(); i++) {
PaletteColor color1 = colors.get(i);
for (int j = i+1; j < colors.size(); j++) {
PaletteColor color2 = colors.get(j);
if (color1 == color2) {
continue;
}
float distance = getPixelDistance(color1, color2);
if (index1 == -1 || distance < minDistance) {
index1 = i;
index2 = j;
minDistance = distance;
}
}
}
PaletteColor color1 = colors.get(index1);
PaletteColor color2 = colors.get(index2);
switch (reductionStrategy) {
case BETTER_CONTRAST:
// remove the color with the lower average distance to the other palette colors
int count = 0;
float distance1 = 0;
float distance2 = 0;
for (PaletteColor c : colors) {
if (c != color1 && c != color2) {
count++;
distance1 += getPixelDistance(color1, c);
distance2 += getPixelDistance(color2, c);
}
}
if (count != 0 && distance1 != distance2) {
distance1 /= (float)count;
distance2 /= (float)count;
if (distance1 < distance2) {
// remove color 1;
colors.remove(index1);
color2.indices.addAll(color1.indices);
} else{
// remove color 2;
colors.remove(index2);
color1.indices.addAll(color2.indices);
}
break;
}
//$FALL-THROUGH$
default:
// remove the color with viewer mappings to the input pixels
if (color1.indices.size() < color2.indices.size()) {
// remove color 1;
colors.remove(index1);
color2.indices.addAll(color1.indices);
} else{
// remove color 2;
colors.remove(index2);
color1.indices.addAll(color2.indices);
}
break;
}
}
}
/**
* Creates an initial color palette from the given pixels and the given palette by
* selecting the colors with the nearest distance to the given pixels.
* This method also stores the indices of the corresponding pixels inside the
* returned PaletteColor instances.
*/
public static List<PaletteColor> createInitialPalette(int pixels[], int[] palette){
Map<Integer, Integer> used = new HashMap<>();
ArrayList<PaletteColor> result = new ArrayList<>();
for (int i = 0, l = pixels.length; i < l; i++) {
double bestDistance = Double.MAX_VALUE;
int bestIndex = -1;
int pixel = pixels[i];
int r1 = (pixel >> 16) & 0xFF;
int g1 = (pixel >> 8) & 0xFF;
int b1 = (pixel >> 0) & 0xFF;
for (int k = 0; k < palette.length; k++) {
int pixel2 = palette[k];
int r2 = (pixel2 >> 16) & 0xFF;
int g2 = (pixel2 >> 8) & 0xFF;
int b2 = (pixel2 >> 0) & 0xFF;
double dist = getPixelDistance(r1, g1, b1, r2, g2, b2);
if (dist < bestDistance) {
bestDistance = dist;
bestIndex = k;
}
}
Integer index = used.get(bestIndex);
PaletteColor c;
if (index == null) {
index = result.size();
c = new PaletteColor(palette[bestIndex]);
result.add(c);
used.put(bestIndex, index);
} else{
c = result.get(index);
}
c.indices.add(i);
}
return result;
}
/**
* Creates a simple random color palette
*/
public static int[] createRandomColorPalette(int num_colors){
Random random = new Random(101);
int count = 0;
int[] result = new int[num_colors];
float add = 360f / (float)num_colors;
for(float i = 0; i < 360f && count < num_colors; i += add) {
float hue = i;
float saturation = 90 +random.nextFloat() * 10;
float brightness = 50 + random.nextFloat() * 10;
result[count++] = Color.HSBtoRGB(hue, saturation, brightness);
}
return result;
}
public static int[] createGrayScalePalette(int count){
float[] grays = new float[count];
float step = 1f/(float)count;
grays[0] = 0;
for (int i = 1; i < count-1; i++) {
grays[i]=i*step;
}
grays[count-1]=1;
return createGrayScalePalette(grays);
}
/**
* Returns a grayscale palette based on the given shades of gray
*/
public static int[] createGrayScalePalette(float[] grays){
int[] result = new int[grays.length];
for (int i = 0; i < result.length; i++) {
float f = grays[i];
result[i] = Color.HSBtoRGB(0, 0, f);
}
return result;
}
private static int[] createResultingImage(int[] pixels,List<PaletteColor> paletteColors, boolean dither, int w, int h) {
int[] palette = new int[paletteColors.size()];
for (int i = 0; i < palette.length; i++) {
palette[i] = paletteColors.get(i).color;
}
if (!dither) {
for (PaletteColor c : paletteColors) {
for (int i : c.indices) {
pixels[i] = c.color;
}
}
} else{
FloydSteinbergDither.generateDither(pixels, palette, w, h);
}
return palette;
}
public static int[] quantize(int[] pixels, int widht, int heigth, int[] colorPalette, int max_cols, boolean dither, ReductionStrategy reductionStrategy) {
// create the initial palette by finding the best match colors from the given color palette
List<PaletteColor> paletteColors = createInitialPalette(pixels, colorPalette);
// reduce the palette size to the given number of maximum colors
reducePalette(paletteColors, max_cols, reductionStrategy);
assert paletteColors.size() <= max_cols;
if (paletteColors.size() < max_cols) {
// fill the palette with the nearest remaining colors
List<PaletteColor> remainingColors = new ArrayList<>();
Set<PaletteColor> used = new HashSet<>(paletteColors);
for (int i = 0; i < colorPalette.length; i++) {
int color = colorPalette[i];
PaletteColor c = new PaletteColor(color);
if (!used.contains(c)) {
remainingColors.add(c);
}
}
fillPalette(paletteColors, remainingColors, max_cols);
}
assert paletteColors.size() == max_cols;
// create the resulting image
return createResultingImage(pixels,paletteColors, dither, widht, heigth);
}
static enum ReductionStrategy{
ORIGINAL_COLORS,
BETTER_CONTRAST,
AVERAGE_DISTANCE,
}
public static void main(String args[]) throws IOException {
// input parameters
String imageFileName = args[0];
File file = new File(imageFileName);
boolean dither = true;
int colorPaletteSize = 80;
int max_cols = 3;
max_cols = Math.min(max_cols, colorPaletteSize);
// create some random color palette
// int[] colorPalette = createRandomColorPalette(colorPaletteSize);
int[] colorPalette = createGrayScalePalette(20);
ReductionStrategy reductionStrategy = ReductionStrategy.AVERAGE_DISTANCE;
// show the original image inside a frame
ImageFrame original = new ImageFrame();
original.setImage(file);
original.setTitle("Original Image");
original.setLocation(0, 0);
Image image = original.getImage();
int width = image.getWidth(null);
int heigth = image.getHeight(null);
int pixels[] = getPixels(image);
int[] palette = quantize(pixels, width, heigth, colorPalette, max_cols, dither, reductionStrategy);
// show the reduced image in another frame
ImageFrame reduced = new ImageFrame();
reduced.setImage(width, heigth, pixels);
reduced.setTitle("Quantized Image (" + palette.length + " colors, dither: " + dither + ")");
reduced.setLocation(100, 100);
}
}
POSSIBLE IMPROVEMENTS
1) The used Floyd-Steinberg algorithm does currently only work for palettes with a maximum size of 256 colors. I guess this could be fixed easily, but since the used FloydSteinbergDither class requires quite a lot of conversions at the moment, it would certainly be better to implement the algorithm from scratch so it fits the color model that is used in the end.
2) I believe using another dithering algorithm like scolorq would perhaps be better. On the "To Do List" at the end of their homepage they write:
[TODO:] The ability to fix some colors to a predetermined set (supported by the algorithm but not the current implementation)
So it seems using a fixed palette should be possible for the algorithm. The Photoshop/Gimp plugin Ximagic seems to implement this functionality using scolorq. From their homepage:
Ximagic Quantizer is a Photoshop plugin for image color quantization (color reduction) & dithering.
Provides: Predefined palette quantization
3) The algorithm to fill the palette could perhaps be improved - e.g. by filling the palette with colors depending on their average distance (like in the reduction algorithm). But this should be tested depending on the finally used dithering algorithm.
EDIT: I think I may have answered a slightly different question. jarnbjo pointed out something that may be wrong with my solution, and I realized I misunderstood the question. I'm leaving my answer here for posterity, though.
I may have a solution to this in Matlab. To find the closest color, I used the weights given by Albert Renshaw in a comment here. I used the HSV colorspace, but all inputs to the code were in standard RGB. Greyscale iamges were converted to 3-channel greyscale images.
To select the best colors to use, I seeded kmeans with the test sample palette and then reset the centroids to be the values they were closest to in the sample pallet.
function imo = recolor(im,new_colors,max_colors)
% Convert to HSV
im2 = rgb2hsv(im);
new_colors = rgb2hsv(new_colors);
% Get number of colors in palette
num_colors = uint8(size(new_colors,1));
% Reshape image so every row is a diferent pixel, and every column a channel
% this is necessary for kmeans in Matlab
im2 = reshape(im2, size(im,1)*size(im,2),size(im,3));
% Seed kmeans with sample pallet, drop empty clusters
[IDX, C] = kmeans(im2,max_colors,'emptyaction','drop');
% For each pixel, IDX tells which cluster in C it corresponds to
% C contains the centroids of each cluster
% Because centroids are adjusted from seeds, we need to select which original color
% in the palette it corresponds to. We cannot be sure that the centroids in C correspond
% to their seed values
% Note that Matlab starts indexing at 1 instead of 0
for i=1:size(C,1)
H = C(i,1);
S = C(i,2);
V = C(i,3);
bdel = 100;
% Find which color in the new_colors palette is closest
for j=1:size(new_colors,1)
H2 = new_colors(j,1);
S2 = new_colors(j,2);
V2 = new_colors(j,3);
dH = (H2-H)^2*0.475;
dS = (S2-S)^2*0.2875;
dV = (V2-V)^2*0.2375;
del = sqrt(dH+dS+dV);
if isnan(del)
continue
end
% update if the new delta is lower than the best
if del<bdel
bdel = del;
C(i,:) = new_colors(j,:);
end
end
end
% Update the colors, this is equal to the following
% for i=1:length(imo)
% imo(i,:) = C(IDX(i),:)
imo = C(IDX,:);
% put it back in its original shape
imo = reshape(imo, size(im));
imo = hsv2rgb(imo);
imshow(imo);
The problem with it right now as I have it written is that it is very slow for color images (Lenna took several minutes).
Is this along the lines of what you are looking for?
Examples.
If you don't understand all the Matlab notation, let me know.
First of all I'd like to insist on the fact that this is no advanced distance color computation.
So far I assumed the first palette is one you either configured or precalculated from an image.
Here, I only configured it and focused on the subpalette extraction problem. I did not use an algorithm, it's highly probable that it may not be the best.
Store an image into a canvas 2d context which will serve as a buffer, I'll refer to it as ctxHidden
Store pixels data of ctxHidden into a variable called img
Loop through entire img with function constraintImageData(img, palette) which accepts as argument img and the palette to transform current img pixels to given colors with the help of the distance function nearestColor(palette, r, g, b, a). Note that this function returns a witness, which basically counts how many times each colors of the palette being used at least once. My example also applies a Floyd-Steinberg dithering, even though you mentionned it was not a problem.
Use the witness to sort descending by colors apparition frequency (from the palette)
Extract these colors from the initial palette to get a subpalette according to maxColors (or max_colors)
Draw the image with the final subpalette, from ctxHidden original data.
You must expect your final image to give you squishy results if maxColors is too low or if your original palette is too distant from the original image colors.
I did a jsfiddle with processing.js, and it is clearly not necessary here but I started using it so I left it as is.
Now here is what the code looks like (the second canvas is the result, applying the final subpalette with a delay of 3 seconds)
var image = document.getElementById('original'),
palettePanel = document.getElementById('palette'),
subPalettePanel = document.getElementById('subpalette'),
canvas = document.getElementById('main'),
maxColors = 12,
palette = [
0x7F8FB1FF,
0x000000FF,
0x404c00FF,
0xe46501FF,
0x722640FF,
0x40337fFF,
0x666666FF,
0x0e5940FF,
0x1bcb01FF,
0xbfcc80FF,
0x333333FF,
0x0033CCFF,
0x66CCFFFF,
0xFF6600FF,
0x000033FF,
0xFFCC00FF,
0xAA0033FF,
0xFF00FFFF,
0x00FFFFFF,
0x123456FF
],
nearestColor = function (palette, r, g, b, a) {
var rr, gg, bb, aa, color, closest,
distr, distg, distb, dista,
dist,
minDist = Infinity;
for (var i = 0; i < l; i++) {
color = palette[i];
rr = palette[i] >> 24 & 0xFF;
gg = palette[i] >> 16 & 0xFF;
bb = palette[i] >> 8 & 0xFF;
aa = palette[i] & 0xFF;
if (closest === undefined) {
closest = color;
}
// compute abs value
distr = Math.abs(rr - r);
distg = Math.abs(gg - g);
distb = Math.abs(bb - b);
dista = Math.abs(aa - a);
dist = (distr + distg + distb + dista * .5) / 3.5;
if (dist < minDist) {
closest = color;
minDist = dist;
}
}
return closest;
},
subpalette = [],
i, l = palette.length,
r, g, b, a,
img,
size = 5,
cols = palettePanel.width / size,
drawPalette = function (p, palette) {
var i, l = palette.length;
p.setup = function () {
p.size(50,50);
p.background(255);
p.noStroke();
for (i = 0; i < l; i++) {
r = palette[i] >> 24 & 0xFF;
g = palette[i] >> 16 & 0xFF;
b = palette[i] >> 8 & 0xFF;
a = palette[i] & 0xFF;
p.fill(r,g,b,a);
p.rect (i%cols*size, ~~(i/cols)*size, size, size);
}
}
},
constraintImageDataToPalette = function (img, palette) {
var i, l, x, y, index,
pixel, x, y,
right, bottom, bottomLeft, bottomRight,
color,
r, g, b, a, i, l,
pr, pg, pb, pa,
rErrorBase,
gErrorBase,
bErrorBase,
aErrorBase,
index,
w = img.width,
w4 = w*4,
h = img.height,
witness = {};
for (i = 0, l = w*h*4; i < l; i += 4) {
x = (i%w);
y = ~~(i/w);
index = x + y*w;
right = index + 4,
bottomLeft = index - 4 + w4,
bottom = index + w4,
bottomRight = index + w4 + 4,
pixel = img.data;
r = pixel[index];
g = pixel[index+1];
b = pixel[index+2];
a = pixel[index+3];
color = nearestColor(palette, r,g,b,a);
witness[color] = (witness[color] || 0) + 1;
// explode channels
pr = color >> 24 & 0xFF;
pg = color >> 16 & 0xFF;
pb = color >> 8 & 0xFF;
pa = color & 0xFF;
// set new color
pixel[index] = pr;
pixel[index+1] = pg;
pixel[index+2] = pb;
pixel[index+3] = pa;
// calculate error
rErrorBase = (r - pr);
gErrorBase = (g - pg);
bErrorBase = (b - pb);
aErrorBase = (a - pa);
///*
// diffuse error right 7/16 = 0.4375
pixel[right] += 0.4375 * rErrorBase;
pixel[right+1] += 0.4375 * gErrorBase;
pixel[right+2] += 0.4375 * bErrorBase;
pixel[right+3] += 0.4375 * aErrorBase;
// diffuse error bottom-left 3/16 = 0.1875
pixel[bottomLeft] += 0.1875 * rErrorBase;
pixel[bottomLeft+1] += 0.1875 * gErrorBase;
pixel[bottomLeft+2] += 0.1875 * bErrorBase;
pixel[bottomLeft+3] += 0.1875 * aErrorBase;
// diffuse error bottom 5/16 = 0.3125
pixel[bottom] += 0.3125 * rErrorBase;
pixel[bottom+1] += 0.3125 * gErrorBase;
pixel[bottom+2] += 0.3125 * bErrorBase;
pixel[bottom+3] += 0.3125 * aErrorBase;
//diffuse error bottom-right 1/16 = 0.0625
pixel[bottomRight] += 0.0625 * rErrorBase;
pixel[bottomRight+1] += 0.0625 * gErrorBase;
pixel[bottomRight+2] += 0.0625 * bErrorBase;
pixel[bottomRight+3] += 0.0625 * aErrorBase;
//*/
}
return witness;
};
new Processing(palettePanel, function (p) { drawPalette(p, palette); });
image.onload = function () {
var l = palette.length;
new Processing(canvas, function (p) {
// argb 24 bits colors
p.setup = function () {
p.size(300, 200);
p.background(0);
p.noStroke();
var ctx = canvas.getContext('2d'),
ctxHidden = document.getElementById('buffer').getContext('2d'),
img, log = [],
witness = {};
ctxHidden.drawImage(image, 0, 0);
img = ctxHidden.getImageData(0, 0, canvas.width, canvas.height);
// constraint colors to largest palette
witness = constraintImageDataToPalette(img, palette);
// show which colors have been picked from the panel
new Processing(subPalettePanel, function (p) { drawPalette(p, Object.keys(witness)); });
ctx.putImageData(img, 0, 0);
var colorsWeights = [];
for (var key in witness) {
colorsWeights.push([+key, witness[key]]);
}
// sort descending colors by most presents ones
colorsWeights.sort(function (a, b) {
return b[1] - a[1];
});
// get the max_colors first of the colors picked to ensure a higher probability of getting a good color
subpalette = colorsWeights
.slice(0, maxColors)
.map(function (colorValueCount) {
// return the actual color code
return colorValueCount[0];
});
// reset image we previously modified
img = ctxHidden.getImageData(0, 0, canvas.width, canvas.height);
// this time constraint with new subpalette
constraintImageDataToPalette(img, subpalette);
// wait 3 seconds to apply new palette and show exactly how it changed
setTimeout(function () {
new Processing(subPalettePanel, function (p) { drawPalette(p, subpalette); });
ctx.putImageData(img, 0, 0);
}, 3000);
};
});
};
NOTE: I have no experience in java image computation, so I used javascript instead. I tried to comment my code, if you have any question about it I'll answer and explain it.
Below is presented an approach implemented in Java using Marvin Framework. It might be a starting point for solving your problem.
Input:
Palette P with M colors.
Number of Colors N.
Image G
Steps:
Apply the Palette P to the image G by replacing the pixels color to the most similar color (less distance in RGB space) in the palette. The output image has the distribution of palette colors by usage.
Compute an histogram containing each color in the palette and how many times it is used in the image (number of pixels).
Sort the palette by pixel usage, most to less used.
Select the N first items in the sorted list and generate a new palette.
Apply this new palette to the image.
Below is presented the output of this approach.
Original image:
(source: sourceforge.net)
Palette, and the image quantitized with 32, 8, 4 colors:
Source code:
public class ColorQuantizationExample {
public ColorQuantizationExample(){
MarvinImage imageOriginal = MarvinImageIO.loadImage("./res/quantization/lena.jpg");
MarvinImage imageOutput = new MarvinImage(imageOriginal.getWidth(), imageOriginal.getHeight());
Set<Color> palette = loadPalette("./res/quantization/palette_7.png");
quantitize(imageOriginal, imageOutput, palette, 32);
MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_7_32.jpg");
quantitize(imageOriginal, imageOutput, palette, 8);
MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_7_8.jpg");
quantitize(imageOriginal, imageOutput, palette, 4);
MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_7_4.jpg");
palette = loadPalette("./res/quantization/palette_8.png");
quantitize(imageOriginal, imageOutput, palette, 32);
MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_8_32.jpg");
quantitize(imageOriginal, imageOutput, palette, 8);
MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_8_8.jpg");
quantitize(imageOriginal, imageOutput, palette, 4);
MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_8_4.jpg");
}
/**
* Load a set of colors from a palette image.
*/
private Set<Color> loadPalette(String path){
Set<Color> ret = new HashSet<Color>();
MarvinImage image = MarvinImageIO.loadImage(path);
String key;
for(int y=0; y<image.getHeight(); y++){
for(int x=0; x<image.getWidth(); x++){
Color c = new Color
(
image.getIntComponent0(x, y),
image.getIntComponent1(x, y),
image.getIntComponent2(x, y)
);
ret.add(c);
}
}
return ret;
}
private void quantitize(MarvinImage imageIn, MarvinImage imageOut, Set<Color> palette, int colors){
applyPalette(imageIn, imageOut, palette);
HashMap<Color, Integer> hist = getColorHistogram(imageOut);
List<Map.Entry<Color, Integer>> list = new LinkedList<Map.Entry<Color, Integer>>( hist.entrySet() );
Collections.sort( list, new Comparator<Map.Entry<Color, Integer>>()
{
#Override
public int compare( Map.Entry<Color, Integer> o1, Map.Entry<Color, Integer> o2 )
{
return (o1.getValue() > o2.getValue() ? -1: 1);
}
} );
Set<Color> newPalette = reducedPalette(list, colors);
applyPalette(imageOut.clone(), imageOut, newPalette);
}
/**
* Apply a palette to an image.
*/
private void applyPalette(MarvinImage imageIn, MarvinImage imageOut, Set<Color> palette){
Color color;
for(int y=0; y<imageIn.getHeight(); y++){
for(int x=0; x<imageIn.getWidth(); x++){
int red = imageIn.getIntComponent0(x, y);
int green = imageIn.getIntComponent1(x, y);
int blue = imageIn.getIntComponent2(x, y);
color = getNearestColor(red, green, blue, palette);
imageOut.setIntColor(x, y, 255, color.getRed(), color.getGreen(), color.getBlue());
}
}
}
/**
* Reduce the palette colors to a given number. The list is sorted by usage.
*/
private Set<Color> reducedPalette(List<Map.Entry<Color, Integer>> palette, int colors){
Set<Color> ret = new HashSet<Color>();
for(int i=0; i<colors; i++){
ret.add(palette.get(i).getKey());
}
return ret;
}
/**
* Compute color histogram
*/
private HashMap<Color, Integer> getColorHistogram(MarvinImage image){
HashMap<Color, Integer> ret = new HashMap<Color, Integer>();
for(int y=0; y<image.getHeight(); y++){
for(int x=0; x<image.getWidth(); x++){
Color c = new Color
(
image.getIntComponent0(x, y),
image.getIntComponent1(x, y),
image.getIntComponent2(x, y)
);
if(ret.get(c) == null){
ret.put(c, 0);
}
ret.put(c, ret.get(c)+1);
}
}
return ret;
}
private Color getNearestColor(int red, int green, int blue, Set<Color> palette){
Color nearestColor=null, c;
double nearestDistance=Integer.MAX_VALUE;
double tempDist;
Iterator<Color> it = palette.iterator();
while(it.hasNext()){
c = it.next();
tempDist = distance(red, green, blue, c.getRed(), c.getGreen(), c.getBlue());
if(tempDist < nearestDistance){
nearestDistance = tempDist;
nearestColor = c;
}
}
return nearestColor;
}
private double distance(int r1, int g1, int b1, int r2, int g2, int b2){
double dist= Math.pow(r1-r2,2) + Math.pow(g1-g2,2) + Math.pow(b1-b2,2);
return Math.sqrt(dist);
}
public static void main(String args[]){
new ColorQuantizationExample();
}
}

Draw string on ellipse

I'm happy with the output of my clock, however - I am not sure how to properly align my drawString for the numbers which should appear at the tip of each tick mark on the clock.
I am hoping someone might be able show the proper method/formula for this.
private void drawTickMarks(Graphics2D g2)
{
double radius = this.faceRadius;
for (int secs = 0; secs <= 60; secs++)
{
double tickStart;
if (secs % 5 == 0)
tickStart = radius - 15;
else
tickStart = radius - 5;
tick = drawRadii(secs / 60.0, tickStart, radius);
if (secs % 5 == 0)
{
g2.setStroke(new BasicStroke(3));
g2.drawString(""+(secs/5),(int)tick.getX1()+(int)(tick.getX1()-tick.getX2()),
(int)tick.getY1()+(int)(tick.getY1()-tick.getY2()));
}
else
g2.setStroke(new BasicStroke(1));
g2.setColor(Color.WHITE);
g2.draw(tick);
}
}
Thanks to Thinhbk for a valid and correct solution with code and Jon W for the proper steps of coming to a solution.
If you imagine the String enclosed within a box, the x and y values you pass into drawString specify the lower left corner of the box.
I would modify the drawString line as such:
String number = ""+(secs/5);
int xLocation = (int)tick.getX1()+(int)(tick.getX1()-tick.getX2());
int yLocation = (int)tick.getY1()+(int)(tick.getY1()-tick.getY2());
int offsetX = /*Insert some value here to shift the position of all the strings
along the x-axis. Make this an expression that contains number.length(),
so that two-digit numbers are shifted more than one digit numbers. */
int offsetY = /*Insert some value here to shift the position of all the strings along
the y-axis.*/
g2.drawString(number, xLocation + offsetX, yLocation + offsetY);
You'll have to play around with the specific values for offsetX and offsetY to make it look nice.
If you want to be even fancier and make it so that drawString will automatically adjust the location depending on what font is being used, take a look at this and the FontMetrics class. You'll want to make offsetX and offsetY vary depending on width and height of the characters being drawn and whatnot.
As a supplemental to the solution provided by Jon W, I create a method that calculate the offset, and IMO, it looks fine. (#Jon W: sorry for not adding comment to your solution as it's rather long.)
/**
* Calculate the offset *
* #param i
* #return array:
* 0: x offset
* 1: y offset
*/
private int[] calculateOffSet(int i) {
int[] val = new int[2];
int deflt = -12;
if(i == 12) {
val[0] = -15;
val[1] = 9;
}else if (i > 6) {
val[0] = deflt + i - 6 ;
val[1] = i ;
}else {
val[0] = deflt + i ;
val[1] = i + 6;
}
return val;
}
And in your code, just call this:
int xLocation = (int)tick.getX1()+(int)(tick.getX1()-tick.getX2());
int yLocation = (int)tick.getY1()+(int)(tick.getY1()-tick.getY2());
int[] offset = calculateOffSet((secs / 5));
g2.drawString(number, xLocation + offset[0], yLocation + offset[1]);

How to get pixel value of Black and White Image?

I making App in netbeans platform using java Swing and JAI. In this i want to do image processing. I capture .tiff black and white image using X-Ray gun. after that i want to plot histogram of that Black and White image. so, for plot to histogram , first we have to get gray or black and white image pixel value. then we can plot histogram using this pixel value.so, how can i get this pixel value of black and white image?
This should work if you use java.awt.image.BufferedImage.
Since you want to create a histogram, I suppose you will loop through all the pixels. There is the method for returning a single pixel value.
int getRGB(int x, int y)
However, since looping will take place I suppose you'd want to use this one:
int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
When you get the array, use:
int alpha = (pixels[i] >> 24) & 0x000000FF;
int red = (pixels[i] >> 16) & 0x000000FF;
int green = (pixels[i] >>8 ) & 0x000000FF;
int blue = pixels[i] & 0x000000FF;
To extract the channel data. Not sure if the variables can be declared as byte (we are using only one byte of the integer in the array, although byte is signed and different arithmetic takes place - two's complement form), but you can declare them as short.
Then preform some maths on these values, for example:
int average = (red + green + blue) / 3;
This will return the average for the pixel, giving you a point you can use in a simple luminosity histogram.
EDIT:
Regarding histogram creation, I have used this class. It takes the image you want the histogram of as an argument to its setImage(BufferedImage image) method. Use updateHistogram() for array populating. The drawing data is in paintComponent(Graphics g). I must admit, it is sloppy, especially when calculating the offsets, but it can be easily simplified.
Here is the whole class:
class HistogramCtrl extends JComponent
{
BufferedImage m_image;
int[] m_histogramArray = new int[256]; //What drives our histogram
int m_maximumPixels;
public HistogramCtrl(){
m_maximumPixels = 0;
for(short i = 0; i<256; i++){
m_histogramArray[i] = 0;
}
}
void setImage(BufferedImage image){
m_image = image;
updateHistogram();
repaint();
}
void updateHistogram(){
if(m_image == null) return;
int[] pixels = m_image.getRGB(0, 0, m_image.getWidth(), m_image.getHeight(), null, 0, m_image.getWidth());
short currentValue = 0;
int red,green,blue;
for(int i = 0; i<pixels.length; i++){
red = (pixels[i] >> 16) & 0x000000FF;
green = (pixels[i] >>8 ) & 0x000000FF;
blue = pixels[i] & 0x000000FF;
currentValue = (short)((red + green + blue) / 3); //Current value gives the average //Disregard the alpha
assert(currentValue >= 0 && currentValue <= 255); //Something is awfully wrong if this goes off...
m_histogramArray[currentValue] += 1; //Increment the specific value of the array
}
m_maximumPixels = 0; //We need to have their number in order to scale the histogram properly
for(int i = 0; i < m_histogramArray.length;i++){ //Loop through the elements
if(m_histogramArray[i] > m_maximumPixels){ //And find the bigges value
m_maximumPixels = m_histogramArray[i];
}
}
}
protected void paintComponent(Graphics g){
assert(m_maximumPixels != 0);
Rectangle rect = g.getClipBounds();
Color oldColor = g.getColor();
g.setColor(new Color(210,210,210));
g.fillRect((int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight());
g.setColor(oldColor);
String zero = "0";
String thff = "255";
final short ctrlWidth = (short)rect.getWidth();
final short ctrlHeight = (short)rect.getHeight();
final short activeWidth = 256;
final short activeHeight = 200;
final short widthSpacing = (short)((ctrlWidth - activeWidth)/2);
final short heightSpacing = (short)((ctrlHeight - activeHeight)/2);
Point startingPoint = new Point();
final int substraction = -1;
startingPoint.x = widthSpacing-substraction;
startingPoint.y = heightSpacing+activeHeight-substraction;
g.drawString(zero,widthSpacing-substraction - 2,heightSpacing+activeHeight-substraction + 15);
g.drawString(thff,widthSpacing+activeWidth-substraction-12,heightSpacing+activeHeight-substraction + 15);
g.drawLine(startingPoint.x, startingPoint.y, widthSpacing+activeWidth-substraction, heightSpacing+activeHeight-substraction);
g.drawLine(startingPoint.x,startingPoint.y,startingPoint.x,heightSpacing-substraction);
double factorHeight = (double)activeHeight / m_maximumPixels; //The height divided by the number of pixels is the factor of multiplication for the other dots
Point usingPoint = new Point(startingPoint.x,startingPoint.y);
usingPoint.x+=2; //I want to move this two points in order to be able to draw the pixels with value 0 a bit away from the limit
Point tempPoint = new Point();
for(short i = 0; i<256; i++){
tempPoint.x = usingPoint.x;
tempPoint.y = (int)((heightSpacing+activeHeight-substraction) - (m_histogramArray[i] * factorHeight));
if((i!=0 && (i % 20 == 0)) || i == 255){
oldColor = g.getColor();
g.setColor(oldColor.brighter());
//Draw horizontal ruler sections
tempPoint.x = widthSpacing + i;
tempPoint.y = heightSpacing+activeHeight-substraction+4;
g.drawLine(tempPoint.x,tempPoint.y,widthSpacing + i,heightSpacing+activeHeight-substraction-4);
if(i <= 200){
//Draw vertical ruler sections
tempPoint.x = widthSpacing - substraction - 3;
tempPoint.y = heightSpacing+activeHeight-substraction-i;
g.drawLine(tempPoint.x,tempPoint.y,widthSpacing - substraction + 4, heightSpacing+activeHeight-substraction-i);
}
tempPoint.x = usingPoint.x;
tempPoint.y = usingPoint.y;
g.setColor(oldColor);
}
g.drawLine(usingPoint.x, usingPoint.y, tempPoint.x, tempPoint.y);
usingPoint.x++; //Set this to the next point
}
}
}

Categories