Printable prints BufferedImage with incorrect size - java

So yeah what I am trying here is printing a BufferedImage, all works just fine until you see the outcome. The outcome is to big, the print is to large and doesn't it scales up everything when printing for some reason.
I used ((MM * DPI)/25,4) to calculate the correct pixel length according to paper size from Millimeters but when I print it its to big.
This is the code I wrote for it:
package frik.main;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import java.awt.event.*;
import javax.swing.*;
import frik.data.Config;
import frik.utils.ImgUtil;
public class Previewer implements Config, Printable, ActionListener{
private JFrame Frame;
private JPanel ImagePanel;
private JLabel PicLabel;
private JButton PrintButton;
private static BufferedImage before;
private static boolean Scaled;
public Previewer(BufferedImage Image, boolean scaled){
this.before = Image;
this.Scaled = scaled;
loop();
}
public int print(Graphics g, PageFormat pf, int page) throws PrinterException{
if (page > 0) {
return Printable.NO_SUCH_PAGE;
}
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pf.getImageableX(), pf.getImageableY());
g.drawImage(0, 0, null);
return Printable.PAGE_EXISTS;
}
public void actionPerformed(ActionEvent e){
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(this);
boolean ok = job.printDialog();
if (ok) {
try {
job.print();
} catch (PrinterException ex) {
JOptionPane.showMessageDialog(null, "The Printjob did not successfully complete.", "Print Failure.", JOptionPane.WARNING_MESSAGE);
}
}
}
public void loop(){
UIManager.put("swing.boldMetal", Boolean.FALSE);
Frame = new JFrame("Mold Preview");
ImagePanel = new JPanel();
PrintButton = new JButton("Print Mold");
Frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
if(Scaled){
PicLabel = new JLabel(new ImageIcon(ImgUtil.scaleImage(PAPER_WIDTH / 3, PAPER_HEIGHT / 3, before)));
}else if (!Scaled){
PicLabel = new JLabel(new ImageIcon(before));
}
ImagePanel.setBackground(Color.orange);
ImagePanel.add(PicLabel);
Frame.add("Center", ImagePanel);
PrintButton.addActionListener(this);
Frame.add("North", PrintButton);
Frame.pack();
Frame.setVisible(true);
Frame.setResizable(false);
}
public static void main(String args[]){
new Previewer(before, Scaled);
//////////////////////////////
}
}
So if someone could help me scale the image before printing it according to the printers scale factor, because I am assuming the printers DPI is causing this, so that the image is the exact size in Millimeters when printed exact to the size input in pixels.
That would be great.

I'm not sure if this is an answer per se, but it solves one of the "niggly" issues I'm having.
I think the problem I have is you don't have a source DPI, so it's not possible to convert from one context to another. Let's say you have a image of 200x200, what does that actually mean?
Without the DPI it's meaningless. If the image is 300dpi, then we could use pixels / dpi = inches = 200 / 72 = 0.667 inches. Then we can convert that to pixels # 72dpi using inches * dpi = 0.667 * 72 = 48
Now, the question the becomes, how do I get the DPI of an image. That's not nearly as easy as it sounds...
import core.ui.UIUtilities;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class TestDPI {
public static final float INCH_PER_MM = 25.4f;
public static void main(String[] args) {
File imageFile = new File("/path/to/your/image");
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(imageFile);
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
if (!readers.hasNext()) {
throw new IOException("Bad format, no readers");
}
ImageReader reader = readers.next();
reader.setInput(iis);
IIOMetadata meta = reader.getImageMetadata(0);
Node root = meta.getAsTree("javax_imageio_1.0");
NodeList nl = root.getChildNodes();
float horizontalPixelSize = 0;
float verticalPixelSize = 0;
for (int index = 0; index < nl.getLength(); index++) {
Node child = nl.item(index);
if ("Dimension".equals(child.getNodeName())) {
NodeList dnl = child.getChildNodes();
for (int inner = 0; inner < dnl.getLength(); inner++) {
child = dnl.item(inner);
if ("HorizontalPixelSize".equals(child.getNodeName())) {
horizontalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
} else if ("VerticalPixelSize".equals(child.getNodeName())) {
verticalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
}
}
}
}
// As "I" understand it. The horizontalPixelSize and verticalPixelSize
// are the number of millimeters per pixel that should be occupied...
System.out.println((INCH_PER_MM / horizontalPixelSize) + "x" + (INCH_PER_MM / verticalPixelSize));
} catch (IOException ex) {
ex.printStackTrace();
} finally {
try {
iis.close();
} catch (Exception e) {
}
}
}
}
Update with preview example
This example basically uses the images own DPI and a target DPI to produce a "print preview"
My test image is 1667x1609 # 300dpi
300dpi test...
Original size = 1667x1609
cmSize = 14.11396110802889x13.622893474996092
Target (pixel) size = 1667x1609
72dpi test...
Original size = 1667x1609
cmSize = 14.11396110802889x13.622893474996092
Target (pixel) size = 400x386
import static core.ui.ImageUtilities.getScaleFactor;
import static core.ui.ImageUtilities.getScaleFactorToFit;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class TestPrintPreview {
public static void main(String[] args) {
new TestPrintPreview();
}
public TestPrintPreview() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
File imageFile = new File("C:\\hold\\thumbnails\\RentAZilla-300dpi.png");
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(new PreviewPane(imageFile, 300)));
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
// The size of an A4 sheet in CMs
public static final double[] A4_PAPER_SIZE = new double[]{21.0, 29.7};
// The number of CMs per Inch
public static final double CM_PER_INCH = 0.393700787d;
// The number of Inches per CMs
public static final double INCH_PER_CM = 2.545d;
// The numer of Inches per mm's
public static final double INCH_PER_MM = 25.45d;
public class PreviewPane extends JPanel {
private BufferedImage img;
private float targetDPI;
private BufferedImage gridBackground;
public PreviewPane(File imageFile, float outputDPI) {
// This determines the output DPI we want...
targetDPI = outputDPI;
try {
// Get the DPI from the image...
double[] imgDPI = getDPI(imageFile);
// Read the image
img = ImageIO.read(imageFile);
// Output the original size...
System.out.println("Original size = " + img.getWidth() + "x" + img.getHeight());
// Calculate the size of the image in cm's
double cmWidth = pixelsToCms(img.getWidth(), imgDPI[0]);
double cmHeight = pixelsToCms(img.getHeight(), imgDPI[1]);
System.out.println("cmSize = " + cmWidth + "x" + cmHeight);
// Calculate the new image size based on the target DPI and
// the cm size of the image...
int imgWidth = (int) Math.round(cmsToPixel(cmWidth, targetDPI));
int imgHeight = (int) Math.round(cmsToPixel(cmHeight, targetDPI));
System.out.println("Target size = " + imgWidth + "x" + imgHeight);
// Create a scaled instance of the image to fit within the
// target boundries
img = getScaledInstanceToFit(img, new Dimension(imgWidth, imgHeight));
} catch (IOException ex) {
Logger.getLogger(TestPrintPreview.class.getName()).log(Level.SEVERE, null, ex);
}
setBackground(Color.WHITE);
}
#Override
public Dimension getPreferredSize() {
// Return the size of the component based on the size of
// an A4 sheet of paper and the target DPI
return new Dimension(
(int) Math.round(cmsToPixel(A4_PAPER_SIZE[0], targetDPI)),
(int) Math.round(cmsToPixel(A4_PAPER_SIZE[1], targetDPI)));
}
/**
* Generates a grid of 1x1 cm cells. This is used to allow you
* to compare the differences of different DPI and ensure that the
* output is what you are expecting...
* #return
*/
protected BufferedImage getGridBackground() {
if (gridBackground == null) {
// Calculate the width and height we need...
int width = (int) Math.round(cmsToPixel(A4_PAPER_SIZE[0], targetDPI));
int height = (int) Math.round(cmsToPixel(A4_PAPER_SIZE[1], targetDPI));
// Create the grid...
gridBackground = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = gridBackground.createGraphics();
// Calculate the size of each cell (1cm square)
double cmAsPixel = cmsToPixel(1, targetDPI);
float xPos = 0;
float yPos = 0;
g2d.setColor(new Color(225, 0, 0, 128));
int count = 0;
Font font = g2d.getFont();
g2d.setFont(font.deriveFont(8f));
FontMetrics fm = g2d.getFontMetrics();
// Draw the horizontal lines
while (xPos < gridBackground.getWidth()) {
g2d.draw(new Line2D.Float(xPos, 0, xPos, gridBackground.getHeight()));
// Add the text markers...
String text = (count++) + "cm";
float x = xPos - fm.stringWidth(text);
g2d.drawString(text, x, fm.getAscent());
xPos += cmAsPixel;
}
// Draw the vertical lines
count = 0;
while (yPos < gridBackground.getHeight()) {
g2d.draw(new Line2D.Float(0, yPos, gridBackground.getWidth(), yPos));
// Add the text markers
String text = (count++) + "cm";
float y = (yPos - fm.getHeight()) + fm.getAscent();
g2d.drawString(text, 0, y);
yPos += cmAsPixel;
}
g2d.dispose();
}
return gridBackground;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// Paint the image...
g2d.drawImage(img, 0, 0, this);
// Paint the grid...
g2d.drawImage(getGridBackground(), 0, 0, this);
g2d.dispose();
}
}
/**
* Converts the given pixels to cm's based on the supplied DPI
* #param pixels
* #param dpi
* #return
*/
public static double pixelsToCms(double pixels, double dpi) {
return inchesToCms(pixels / dpi);
}
/**
* Converts the given cm's to pixels based on the supplied DPI
* #param cms
* #param dpi
* #return
*/
public static double cmsToPixel(double cms, double dpi) {
return cmToInches(cms) * dpi;
}
/**
* Converts the given cm's to inches
* #param cms
* #return
*/
public static double cmToInches(double cms) {
return cms * CM_PER_INCH;
}
/**
* Converts the given inches to cm's
* #param inch
* #return
*/
public static double inchesToCms(double inch) {
return inch * INCH_PER_CM;
}
/**
* Gets the DPI for the specified image. This does return the horizontal
* and vertical DPI, but you could conceivably use just use one of the values
* #param imageFile
* #return
* #throws IOException
*/
public double[] getDPI(File imageFile) throws IOException {
double[] dpi = new double[]{72, 72};
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(imageFile);
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
if (!readers.hasNext()) {
throw new IOException("Bad format, no readers");
}
ImageReader reader = readers.next();
reader.setInput(iis);
IIOMetadata meta = reader.getImageMetadata(0);
Node root = meta.getAsTree("javax_imageio_1.0");
NodeList nl = root.getChildNodes();
float horizontalPixelSize = 0;
float verticalPixelSize = 0;
for (int index = 0; index < nl.getLength(); index++) {
Node child = nl.item(index);
if ("Dimension".equals(child.getNodeName())) {
NodeList dnl = child.getChildNodes();
for (int inner = 0; inner < dnl.getLength(); inner++) {
child = dnl.item(inner);
if ("HorizontalPixelSize".equals(child.getNodeName())) {
horizontalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
} else if ("VerticalPixelSize".equals(child.getNodeName())) {
verticalPixelSize = Float.parseFloat(child.getAttributes().getNamedItem("value").getNodeValue());
}
}
}
}
dpi = new double[]{(INCH_PER_MM / horizontalPixelSize), (INCH_PER_MM / verticalPixelSize)};
} finally {
try {
iis.close();
} catch (Exception e) {
}
}
return dpi;
}
/**
* Returns a scaled instance of the image to fit within the specified
* area. This means that the image is guaranteed to be <= size.width and
* <= size.height
* #param img
* #param size
* #return
*/
public static BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
double scaleFactor = getScaleFactorToFit(img, size);
return getScaledInstance(img, scaleFactor);
}
public static double getScaleFactorToFit(BufferedImage img, Dimension size) {
double dScale = 1;
if (img != null) {
int imageWidth = img.getWidth();
int imageHeight = img.getHeight();
dScale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
}
return dScale;
}
/**
* Returns the required scale factor to fit the original size into the toFit
* size.
* #param original
* #param toFit
* #return
*/
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
/**
* Returns the scale factor required to go from the master size to the
* target size
* #param iMasterSize
* #param iTargetSize
* #return
*/
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
return (double) iTargetSize / (double) iMasterSize;
}
/**
* Returns a scaled instance of the image based on the supplied scale factor.
*
* The images width and height are multiplied by the supplied scale factor
* #param img
* #param dScaleFactor
* #return
*/
protected static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
BufferedImage imgScale = img;
int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);
if (dScaleFactor <= 1.0d) {
imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight);
} else {
imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight);
}
return imgScale;
}
/**
* Scales the specified image down to be less then equal to the target width
* and height.
*
* The image is scaled using a divide an conquer approach to provide
* the best scaling possible
* #param img
* #param targetWidth
* #param targetHeight
* #return
*/
protected static BufferedImage getScaledDownInstance(BufferedImage img,
int targetWidth,
int targetHeight) {
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w, h;
w = img.getWidth();
h = img.getHeight();
do {
if (w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
/**
/**
* Scales the specified image up
*
* The image is scaled using a divide an conquer approach to provide
* the best scaling possible
* #param img
* #param targetWidth
* #param targetHeight
* #return
*/
protected static BufferedImage getScaledUpInstance(BufferedImage img,
int targetWidth,
int targetHeight) {
int type = BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
w = img.getWidth();
h = img.getHeight();
do {
if (w < targetWidth) {
w *= 2;
if (w > targetWidth) {
w = targetWidth;
}
}
if (h < targetHeight) {
h *= 2;
if (h > targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
tmp = null;
} while (w != targetWidth || h != targetHeight);
return ret;
}
}

Related

Calculate the padding to center-align a rectangle (resized by percentage)

Below is my current algorithm to align the rectangle (representing symbol) in the center of the canvas space (representing icon). It is only the algorithm I am interested in so ignore the rest of the code as it is merely for demonstration purposes as a visual aid.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class IconSymbol extends JFrame {
public IconSymbol(double iWH, double s, double w, double h) {
getContentPane().add(new Canvas((int)iWH, s, w, h));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize((int)iWH, (int)iWH);
setVisible(true);
}
public static void main(String arg[]) {
IconSymbol is = new IconSymbol(100, 0.9, 50, 50);
}
class Canvas extends JPanel {
// STIPULATED
double iconWH = 0;
double sScale = 0;
double sWidth = 0;
double sHeight = 0;
// CALCULATED
double padX = 0;
double padY = 0;
double xOffSet = 0;
double yOffSet = 0;
public Canvas(double iWH,double sS,double sW,double sH) {
this.iconWH = iWH;
this.sScale = sS;
this.sWidth = sW;
this.sHeight = sH;
}
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
g2D.setBackground(Color.WHITE);
g2D.setPaint(Color.BLUE);
Shape icon = new Rectangle.Double(0,0,(int)iconWH,(int)iconWH);
g2D.fill(icon);
g2D.setPaint(Color.BLACK);
int width = (int)iconWH / 10;
int height= (int)iconWH / 10;
for(int row=0;row<10;row++){
for(int col=0;col<10;col++){
g.drawRect(row*width,col*height,width,height);
}
}
Point off = algorithm();
g2D.setPaint(Color.RED);
Shape s = new Rectangle.Double(off.x,off.y,(int)sWidth,(int)sHeight);
AffineTransform tran = AffineTransform.getScaleInstance(sScale, sScale);
g2D.fill(tran.createTransformedShape(s));
}
public Point algorithm(){
// ALGORITHM WITH EXACT NEEDED PARAMETERS
padX = (sWidth - ((sWidth * sScale))) / 2;
padY = (sHeight - ((sHeight * sScale))) / 2;
xOffSet = padX + ((iconWH - (sWidth * sScale)) / 2);
yOffSet = padX + ((iconWH - (sHeight * sScale)) / 2);
Point point = new Point((int)xOffSet, (int)yOffSet);
return point;
}
}
}
The problem with your code is that the scale transform tran is scaling the calculated origin of the rectangle, off, as well as sWidth and sHeight. If you want to keep with your current scheme you need to apply the inverse of the scale transform to the calculated offset in your algorithm method:
public Point algorithm(){
// ALGORITHM WITH EXACT NEEDED PARAMETERS
xOffSet = ((iconWH - (sWidth * sScale)) / 2) / sScale;
yOffSet = ((iconWH - (sHeight * sScale)) / 2) / sScale;
Point point = new Point((int)xOffSet, (int)yOffSet);
return point;
}
Note that I removed padX and padY as they weren't required for calculating the offset.

Make a jpg a Shape [duplicate]

I was wondering whether there is any way to convert an image/graphic into a Shape? For example, can I convert the outline of a motorcycle shape into a Shape so I can then use it in Java? I know you can do it with normal squares or with rounded corners, polygons, etc. But is there a way to do a custom shape?
motorcycle.jpg
motorcycle-03.png
ImageOutline.java
This code requires some patience (when running).
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.geom.Area;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.Date;
import javax.swing.*;
/* Motorcycle image courtesy of ShutterStock
http://www.shutterstock.com/pic-13585165/stock-vector-travel-motorcycle-silhouette.html */
class ImageOutline {
public static Area getOutline(BufferedImage image, Color color, boolean include, int tolerance) {
Area area = new Area();
for (int x=0; x<image.getWidth(); x++) {
for (int y=0; y<image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x,y));
if (include) {
if (isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x,y,1,1);
area.add(new Area(r));
}
} else {
if (!isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x,y,1,1);
area.add(new Area(r));
}
}
}
}
return area;
}
public static boolean isIncluded(Color target, Color pixel, int tolerance) {
int rT = target.getRed();
int gT = target.getGreen();
int bT = target.getBlue();
int rP = pixel.getRed();
int gP = pixel.getGreen();
int bP = pixel.getBlue();
return(
(rP-tolerance<=rT) && (rT<=rP+tolerance) &&
(gP-tolerance<=gT) && (gT<=gP+tolerance) &&
(bP-tolerance<=bT) && (bT<=bP+tolerance) );
}
public static BufferedImage drawOutline(int w, int h, Area area) {
final BufferedImage result = new BufferedImage(
w,
h,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = result.createGraphics();
g.setColor(Color.white);
g.fillRect(0,0,w,h);
g.setClip(area);
g.setColor(Color.red);
g.fillRect(0,0,w,h);
g.setClip(null);
g.setStroke(new BasicStroke(1));
g.setColor(Color.blue);
g.draw(area);
return result;
}
public static BufferedImage createAndWrite(
BufferedImage image,
Color color,
boolean include,
int tolerance,
String name)
throws Exception {
int w = image.getWidth();
int h = image.getHeight();
System.out.println("Get Area: " + new Date() + " - " + name);
Area area = getOutline(image, color, include, tolerance);
System.out.println("Got Area: " + new Date() + " - " + name);
final BufferedImage result = drawOutline(w,h,area);
displayAndWriteImage(result, name);
return result;
}
public static void displayAndWriteImage(BufferedImage image, String fileName) throws Exception {
ImageIO.write(image, "png", new File(fileName));
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(image)));
}
public static void main(String[] args) throws Exception {
final BufferedImage outline = ImageIO.read(new File("motorcycle.jpg"));
BufferedImage crop = outline.getSubimage(17,35,420,270);
displayAndWriteImage(crop, "motorcycle-01.png");
BufferedImage crude = createAndWrite(crop, Color.white, false, 60, "motorcycle-02.png");
BufferedImage combo = createAndWrite(crude, Color.red, true, 0, "motorcycle-03.png");
}
}
function getArea_FastHack is build upon Andrew Thompsons work, which was very helpful.
Mine should be faster, however:
(//Edit: and sloppier, too)
import java.awt.*;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
/**
* CustomShape
* based on a Class from Andrew Thompson *
* Source: http://stackoverflow.com/questions/7052422/image-graphic-into-a-shape-in-java/7059497#7059497
* #author Samuel Schneider, Andrew Thompson
*
*
*/
class CustomShape {
private BufferedImage image=null;
/**
* Creates an Area with PixelPerfect precision
* #param color The color that is draws the Custom Shape
* #param tolerance The color tolerance
* #return Area
*/
public Area getArea(Color color, int tolerance) {
if(image==null) return null;
Area area = new Area();
for (int x=0; x<image.getWidth(); x++) {
for (int y=0; y<image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x,y));
if (isIncluded(color, pixel, tolerance)) {
Rectangle r = new Rectangle(x,y,1,1);
area.add(new Area(r));
}
}
}
return area;
}
public Area getArea_FastHack() {
//Assumes Black as Shape Color
if(image==null) return null;
Area area = new Area();
Rectangle r;
int y1,y2;
for (int x=0; x<image.getWidth(); x++) {
y1=99;
y2=-1;
for (int y=0; y<image.getHeight(); y++) {
Color pixel = new Color(image.getRGB(x,y));
//-16777216 entspricht RGB(0,0,0)
if (pixel.getRGB()==-16777216) {
if(y1==99) {y1=y;y2=y;}
if(y>(y2+1)) {
r = new Rectangle(x,y1,1,y2-y1);
area.add(new Area(r));
y1=y;y2=y;
}
y2=y;
}
}
if((y2-y1)>=0) {
r = new Rectangle(x,y1,1,y2-y1);
area.add(new Area(r));
}
}
return area;
}
public static boolean isIncluded(Color target, Color pixel, int tolerance) {
int rT = target.getRed();
int gT = target.getGreen();
int bT = target.getBlue();
int rP = pixel.getRed();
int gP = pixel.getGreen();
int bP = pixel.getBlue();
return(
(rP-tolerance<=rT) && (rT<=rP+tolerance) &&
(gP-tolerance<=gT) && (gT<=gP+tolerance) &&
(bP-tolerance<=bT) && (bT<=bP+tolerance) );
}
public CustomShape(String path) {
try {
BufferedImage image = ImageIO.read(new File(path));
this.image = image;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Here's something faster but less accurate, useful for collision checking or 2D physics.
Point[] MakePoly(BufferedImage spr,int d,int angle){
//creates an outline of a transparent image, points are stored in an array
//arg0 - BufferedImage source image
//arg1 - Int detail (lower = better)
//arg2 - Int angle threshold in degrees (will remove points with angle differences below this level; 15 is a good value)
// making this larger will make the body faster but less accurate;
int w= spr.getWidth(null); int h= spr.getHeight(null);
// increase array size from 255 if needed
int[] vertex_x=new int[255], vertex_y=new int[255], vertex_k=new int[255];
int numPoints=0, tx=0,ty=0,fy=-1,lx=0,ly=0; vertex_x[0]=0; vertex_y[0]=0; vertex_k[0]=1;
for (tx=0;tx<w;tx+=d) for (ty=0;ty<h;ty+=1) if((spr.getRGB(tx,ty)>>24) != 0x00 )
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints++; if (fy<0) fy=ty; lx=tx; ly=ty; break; }
for (ty=0;ty<h;ty+=d) for (tx=w-1;tx>=0;tx-=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && ty > ly)
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints++; lx=tx; ly=ty; break; }
for (tx=w-1;tx>=0;tx-=d) for (ty=h-1;ty>=0;ty-=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && tx < lx)
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints ++; lx=tx; ly=ty; break; }
for (ty=h-1;ty>=0;ty-=d) for (tx=0;tx<w;tx+=1) if((spr.getRGB(tx,ty)>>24) != 0x00 && ty < ly && ty > fy)
{vertex_x[numPoints]=tx; vertex_y[numPoints]=h-ty; vertex_k[numPoints]=1; numPoints ++; lx=tx; ly=ty; break; }
double ang1,ang2; for (int i=0;i<numPoints-2;i++) {
ang1 = PointDirection(vertex_x[i],vertex_y[i], vertex_x[i+1],vertex_y[i+1]);
ang2 = PointDirection(vertex_x[i+1],vertex_y[i+1], vertex_x[i+2],vertex_y[i+2]);
if (Math.abs(ang1-ang2) <= angle) vertex_k[i+1] = 0; }
ang1 = PointDirection(vertex_x[numPoints-2],vertex_y[numPoints-2], vertex_x[numPoints-1],vertex_y[numPoints-1]);
ang2 = PointDirection(vertex_x[numPoints-1],vertex_y[numPoints-1], vertex_x[0],vertex_y[0]);
if (Math.abs(ang1-ang2) <= angle) vertex_k[numPoints-1] = 0;
ang1 = PointDirection(vertex_x[numPoints-1],vertex_y[numPoints-1], vertex_x[0],vertex_y[0]);
ang2 = PointDirection(vertex_x[0],vertex_y[0], vertex_x[1],vertex_y[1]);
if (Math.abs(ang1-ang2) <= angle) vertex_k[0] = 0;
int n=0;for (int i=0;i<numPoints;i++)if(vertex_k[i]==1)n++;
Point[] poly= new Point[n]; n=0; for (int i=0;i<numPoints;i++) if (vertex_k[i]==1)
{ poly[n]=new Point(); poly[n].x=vertex_x[i]; poly[n].y=h-vertex_y[i];n++;} return poly;
}
double PointDirection(double xfrom,double yfrom,double xto,double yto){
return Math.atan2(yto-yfrom,xto-xfrom)*180/Math.PI ;
}

fillArc() in Java not blending color as expected

I'm trying to blend all the colors into a circle using arcs.
However, the arc comes as one solid color and not a blend of color as I thought.
Is it possible to?
public static void main(String[] args) {
DrawingPanel panel = new DrawingPanel(512,512);
Graphics g = panel.getGraphics();
int width = 100;
int height = 100;
g.drawOval(0,0,width, height);
//yellow
for( int i = 0; i < 100 ; i++){
Color c = new Color(255/100*i,255,0);
g.setColor(c);
g.fillArc(0,0,width,height,95,11);
}
g.fillArc(0,0,width,height,95,11);
You need to change the arc angle for every iteration and the arc size should be fixed at a certain value. I'm not sure what the value would be because I would expect you should iterate 360 times (in which case the size would be 1), not 100.
You can use the HSL Color class to do this simply.
An HSL color allows you to change the "hue" of the color in degrees. So you just need a simple loop to set/paint the color in a 1 degree arc:
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
public class ColorWheel extends JPanel
{
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
HSLColor color = new HSLColor( Color.YELLOW );
for (int i = 0; i < 360; i++)
{
g.setColor( color.adjustHue(i) );
g.fillArc( 25, 25, 200, 200, i, 1);
}
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(250, 250);
}
private static void createAndShowGUI()
{
JComponent wheel = new ColorWheel();
JFrame frame = new JFrame("Color Wheel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane( wheel );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
I used the HSL Color because it has a simple API to change the hue.
If you don't want to use that class then you can use Color.getHSBColor(...) method to get the color for each degree of change. Again the saturation and brightness would be fixed values and then you just change the hue.
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
float hueDegree = 1 / 360.0f;
for (int i = 0; i < 360; i++)
{
Color color = Color.getHSBColor(i * hueDegree, 1.0f, 1.0f);
g.setColor( color );
g.fillArc( 25, 25, 200, 200, i, 1);
}
}
Along time ago, I stumbled across a ConicalGradientPaint from Harmonic Code (there source is there somewhere, I just can't seem to find it again, but I've included my copy of it in the example).
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ColorWheel {
public static void main(String[] args) {
new ColorWheel();
}
public ColorWheel() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
ConicalGradientPaint rgp = new ConicalGradientPaint(
true,
new Point(getWidth() / 2, getHeight() / 2),
0.5f,
new float[]{0, 60, 120, 180, 240, 300, 360},
new Color[]{Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.RED});
g2d.setPaint(rgp);
int radius = Math.min(getWidth(), getHeight());
int x = (getWidth() - radius) / 2;
int y = (getHeight() - radius) / 2;
g2d.fillOval(x, y, radius, radius);
g2d.dispose();
}
}
public final class ConicalGradientPaint implements java.awt.Paint {
private final java.awt.geom.Point2D CENTER;
private final double[] FRACTION_ANGLES;
private final double[] RED_STEP_LOOKUP;
private final double[] GREEN_STEP_LOOKUP;
private final double[] BLUE_STEP_LOOKUP;
private final double[] ALPHA_STEP_LOOKUP;
private final java.awt.Color[] COLORS;
private static final float INT_TO_FLOAT_CONST = 1f / 255f;
/**
* Standard constructor which takes the FRACTIONS in values from 0.0f to
* 1.0f
*
* #param CENTER
* #param GIVEN_FRACTIONS
* #param GIVEN_COLORS
* #throws IllegalArgumentException
*/
public ConicalGradientPaint(final java.awt.geom.Point2D CENTER, final float[] GIVEN_FRACTIONS, final java.awt.Color[] GIVEN_COLORS) throws IllegalArgumentException {
this(false, CENTER, 0.0f, GIVEN_FRACTIONS, GIVEN_COLORS);
}
/**
* Enhanced constructor which takes the FRACTIONS in degress from 0.0f to
* 360.0f and also an GIVEN_OFFSET in degrees around the rotation CENTER
*
* #param USE_DEGREES
* #param CENTER
* #param GIVEN_OFFSET
* #param GIVEN_FRACTIONS
* #param GIVEN_COLORS
* #throws IllegalArgumentException
*/
public ConicalGradientPaint(final boolean USE_DEGREES, final java.awt.geom.Point2D CENTER, final float GIVEN_OFFSET, final float[] GIVEN_FRACTIONS, final java.awt.Color[] GIVEN_COLORS) throws IllegalArgumentException {
// Check that fractions and colors are of the same size
if (GIVEN_FRACTIONS.length != GIVEN_COLORS.length) {
throw new IllegalArgumentException("Fractions and colors must be equal in size");
}
final java.util.ArrayList<Float> FRACTION_LIST = new java.util.ArrayList<Float>(GIVEN_FRACTIONS.length);
final float OFFSET;
if (USE_DEGREES) {
final double DEG_FRACTION = 1f / 360f;
if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), -0.5) == 0) {
OFFSET = -0.5f;
} else if (Double.compare((GIVEN_OFFSET * DEG_FRACTION), 0.5) == 0) {
OFFSET = 0.5f;
} else {
OFFSET = (float) (GIVEN_OFFSET * DEG_FRACTION);
}
for (float fraction : GIVEN_FRACTIONS) {
FRACTION_LIST.add((float) (fraction * DEG_FRACTION));
}
} else {
// Now it seems to work with rotation of 0.5f, below is the old code to correct the problem
// if (GIVEN_OFFSET == -0.5)
// {
// // This is needed because of problems in the creation of the Raster
// // with a angle offset of exactly -0.5
// OFFSET = -0.49999f;
// }
// else if (GIVEN_OFFSET == 0.5)
// {
// // This is needed because of problems in the creation of the Raster
// // with a angle offset of exactly +0.5
// OFFSET = 0.499999f;
// }
// else
{
OFFSET = GIVEN_OFFSET;
}
for (float fraction : GIVEN_FRACTIONS) {
FRACTION_LIST.add(fraction);
}
}
// Check for valid offset
if (OFFSET > 0.5f || OFFSET < -0.5f) {
throw new IllegalArgumentException("Offset has to be in the range of -0.5 to 0.5");
}
// Adjust fractions and colors array in the case where startvalue != 0.0f and/or endvalue != 1.0f
final java.util.List<java.awt.Color> COLOR_LIST = new java.util.ArrayList<java.awt.Color>(GIVEN_COLORS.length);
COLOR_LIST.addAll(java.util.Arrays.asList(GIVEN_COLORS));
// Assure that fractions start with 0.0f
if (FRACTION_LIST.get(0) != 0.0f) {
FRACTION_LIST.add(0, 0.0f);
final java.awt.Color TMP_COLOR = COLOR_LIST.get(0);
COLOR_LIST.add(0, TMP_COLOR);
}
// Assure that fractions end with 1.0f
if (FRACTION_LIST.get(FRACTION_LIST.size() - 1) != 1.0f) {
FRACTION_LIST.add(1.0f);
COLOR_LIST.add(GIVEN_COLORS[0]);
}
// Recalculate the fractions and colors with the given offset
final java.util.Map<Float, java.awt.Color> FRACTION_COLORS = recalculate(FRACTION_LIST, COLOR_LIST, OFFSET);
// Clear the original FRACTION_LIST and COLOR_LIST
FRACTION_LIST.clear();
COLOR_LIST.clear();
// Sort the hashmap by fraction and add the values to the FRACION_LIST and COLOR_LIST
final java.util.SortedSet<Float> SORTED_FRACTIONS = new java.util.TreeSet<Float>(FRACTION_COLORS.keySet());
final java.util.Iterator<Float> ITERATOR = SORTED_FRACTIONS.iterator();
while (ITERATOR.hasNext()) {
final float CURRENT_FRACTION = ITERATOR.next();
FRACTION_LIST.add(CURRENT_FRACTION);
COLOR_LIST.add(FRACTION_COLORS.get(CURRENT_FRACTION));
}
// Set the values
this.CENTER = CENTER;
COLORS = COLOR_LIST.toArray(new java.awt.Color[]{});
// Prepare lookup table for the angles of each fraction
final int MAX_FRACTIONS = FRACTION_LIST.size();
this.FRACTION_ANGLES = new double[MAX_FRACTIONS];
for (int i = 0; i < MAX_FRACTIONS; i++) {
FRACTION_ANGLES[i] = FRACTION_LIST.get(i) * 360;
}
// Prepare lookup tables for the color stepsize of each color
RED_STEP_LOOKUP = new double[COLORS.length];
GREEN_STEP_LOOKUP = new double[COLORS.length];
BLUE_STEP_LOOKUP = new double[COLORS.length];
ALPHA_STEP_LOOKUP = new double[COLORS.length];
for (int i = 0; i < (COLORS.length - 1); i++) {
RED_STEP_LOOKUP[i] = ((COLORS[i + 1].getRed() - COLORS[i].getRed()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
GREEN_STEP_LOOKUP[i] = ((COLORS[i + 1].getGreen() - COLORS[i].getGreen()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
BLUE_STEP_LOOKUP[i] = ((COLORS[i + 1].getBlue() - COLORS[i].getBlue()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
ALPHA_STEP_LOOKUP[i] = ((COLORS[i + 1].getAlpha() - COLORS[i].getAlpha()) * INT_TO_FLOAT_CONST) / (FRACTION_ANGLES[i + 1] - FRACTION_ANGLES[i]);
}
}
/**
* Recalculates the fractions in the FRACTION_LIST and their associated
* colors in the COLOR_LIST with a given OFFSET. Because the conical
* gradients always starts with 0 at the top and clockwise direction you
* could rotate the defined conical gradient from -180 to 180 degrees which
* equals values from -0.5 to +0.5
*
* #param FRACTION_LIST
* #param COLOR_LIST
* #param OFFSET
* #return Hashmap that contains the recalculated fractions and colors after
* a given rotation
*/
private java.util.HashMap<Float, java.awt.Color> recalculate(final java.util.List<Float> FRACTION_LIST, final java.util.List<java.awt.Color> COLOR_LIST, final float OFFSET) {
// Recalculate the fractions and colors with the given offset
final int MAX_FRACTIONS = FRACTION_LIST.size();
final java.util.HashMap<Float, java.awt.Color> FRACTION_COLORS = new java.util.HashMap<Float, java.awt.Color>(MAX_FRACTIONS);
for (int i = 0; i < MAX_FRACTIONS; i++) {
// Add offset to fraction
final float TMP_FRACTION = FRACTION_LIST.get(i) + OFFSET;
// Color related to current fraction
final java.awt.Color TMP_COLOR = COLOR_LIST.get(i);
// Check each fraction for limits (0...1)
if (TMP_FRACTION <= 0) {
FRACTION_COLORS.put(1.0f + TMP_FRACTION + 0.0001f, TMP_COLOR);
final float NEXT_FRACTION;
final java.awt.Color NEXT_COLOR;
if (i < MAX_FRACTIONS - 1) {
NEXT_FRACTION = FRACTION_LIST.get(i + 1) + OFFSET;
NEXT_COLOR = COLOR_LIST.get(i + 1);
} else {
NEXT_FRACTION = 1 - FRACTION_LIST.get(0) + OFFSET;
NEXT_COLOR = COLOR_LIST.get(0);
}
if (NEXT_FRACTION > 0) {
final java.awt.Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, NEXT_COLOR, (int) ((NEXT_FRACTION - TMP_FRACTION) * 10000), (int) ((-TMP_FRACTION) * 10000));
FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
}
} else if (TMP_FRACTION >= 1) {
FRACTION_COLORS.put(TMP_FRACTION - 1.0f - 0.0001f, TMP_COLOR);
final float PREVIOUS_FRACTION;
final java.awt.Color PREVIOUS_COLOR;
if (i > 0) {
PREVIOUS_FRACTION = FRACTION_LIST.get(i - 1) + OFFSET;
PREVIOUS_COLOR = COLOR_LIST.get(i - 1);
} else {
PREVIOUS_FRACTION = FRACTION_LIST.get(MAX_FRACTIONS - 1) + OFFSET;
PREVIOUS_COLOR = COLOR_LIST.get(MAX_FRACTIONS - 1);
}
if (PREVIOUS_FRACTION < 1) {
final java.awt.Color NEW_FRACTION_COLOR = getColorFromFraction(TMP_COLOR, PREVIOUS_COLOR, (int) ((TMP_FRACTION - PREVIOUS_FRACTION) * 10000), (int) (TMP_FRACTION - 1.0f) * 10000);
FRACTION_COLORS.put(1.0f, NEW_FRACTION_COLOR);
FRACTION_COLORS.put(0.0f, NEW_FRACTION_COLOR);
}
} else {
FRACTION_COLORS.put(TMP_FRACTION, TMP_COLOR);
}
}
// Clear the original FRACTION_LIST and COLOR_LIST
FRACTION_LIST.clear();
COLOR_LIST.clear();
return FRACTION_COLORS;
}
/**
* With the START_COLOR at the beginning and the DESTINATION_COLOR at the
* end of the given RANGE the method will calculate and return the color
* that equals the given VALUE. e.g. a START_COLOR of BLACK (R:0, G:0, B:0,
* A:255) and a DESTINATION_COLOR of WHITE(R:255, G:255, B:255, A:255) with
* a given RANGE of 100 and a given VALUE of 50 will return the color that
* is exactly in the middle of the gradient between black and white which is
* gray(R:128, G:128, B:128, A:255) So this method is really useful to
* calculate colors in gradients between two given colors.
*
* #param START_COLOR
* #param DESTINATION_COLOR
* #param RANGE
* #param VALUE
* #return Color calculated from a range of values by given value
*/
public java.awt.Color getColorFromFraction(final java.awt.Color START_COLOR, final java.awt.Color DESTINATION_COLOR, final int RANGE, final int VALUE) {
final float SOURCE_RED = START_COLOR.getRed() * INT_TO_FLOAT_CONST;
final float SOURCE_GREEN = START_COLOR.getGreen() * INT_TO_FLOAT_CONST;
final float SOURCE_BLUE = START_COLOR.getBlue() * INT_TO_FLOAT_CONST;
final float SOURCE_ALPHA = START_COLOR.getAlpha() * INT_TO_FLOAT_CONST;
final float DESTINATION_RED = DESTINATION_COLOR.getRed() * INT_TO_FLOAT_CONST;
final float DESTINATION_GREEN = DESTINATION_COLOR.getGreen() * INT_TO_FLOAT_CONST;
final float DESTINATION_BLUE = DESTINATION_COLOR.getBlue() * INT_TO_FLOAT_CONST;
final float DESTINATION_ALPHA = DESTINATION_COLOR.getAlpha() * INT_TO_FLOAT_CONST;
final float RED_DELTA = DESTINATION_RED - SOURCE_RED;
final float GREEN_DELTA = DESTINATION_GREEN - SOURCE_GREEN;
final float BLUE_DELTA = DESTINATION_BLUE - SOURCE_BLUE;
final float ALPHA_DELTA = DESTINATION_ALPHA - SOURCE_ALPHA;
final float RED_FRACTION = RED_DELTA / RANGE;
final float GREEN_FRACTION = GREEN_DELTA / RANGE;
final float BLUE_FRACTION = BLUE_DELTA / RANGE;
final float ALPHA_FRACTION = ALPHA_DELTA / RANGE;
//System.out.println(DISTANCE + " " + CURRENT_FRACTION);
return new java.awt.Color(SOURCE_RED + RED_FRACTION * VALUE, SOURCE_GREEN + GREEN_FRACTION * VALUE, SOURCE_BLUE + BLUE_FRACTION * VALUE, SOURCE_ALPHA + ALPHA_FRACTION * VALUE);
}
#Override
public java.awt.PaintContext createContext(final java.awt.image.ColorModel COLOR_MODEL, final java.awt.Rectangle DEVICE_BOUNDS, final java.awt.geom.Rectangle2D USER_BOUNDS, final java.awt.geom.AffineTransform TRANSFORM, final java.awt.RenderingHints HINTS) {
final java.awt.geom.Point2D TRANSFORMED_CENTER = TRANSFORM.transform(CENTER, null);
return new ConicalGradientPaintContext(TRANSFORMED_CENTER);
}
#Override
public int getTransparency() {
return java.awt.Transparency.TRANSLUCENT;
}
private final class ConicalGradientPaintContext implements java.awt.PaintContext {
final private java.awt.geom.Point2D CENTER;
public ConicalGradientPaintContext(final java.awt.geom.Point2D CENTER) {
this.CENTER = new java.awt.geom.Point2D.Double(CENTER.getX(), CENTER.getY());
}
#Override
public void dispose() {
}
#Override
public java.awt.image.ColorModel getColorModel() {
return java.awt.image.ColorModel.getRGBdefault();
}
#Override
public java.awt.image.Raster getRaster(final int X, final int Y, final int TILE_WIDTH, final int TILE_HEIGHT) {
final double ROTATION_CENTER_X = -X + CENTER.getX();
final double ROTATION_CENTER_Y = -Y + CENTER.getY();
final int MAX = FRACTION_ANGLES.length;
// Create raster for given colormodel
final java.awt.image.WritableRaster RASTER = getColorModel().createCompatibleWritableRaster(TILE_WIDTH, TILE_HEIGHT);
// Create data array with place for red, green, blue and alpha values
int[] data = new int[(TILE_WIDTH * TILE_HEIGHT * 4)];
double dx;
double dy;
double distance;
double angle;
double currentRed = 0;
double currentGreen = 0;
double currentBlue = 0;
double currentAlpha = 0;
for (int py = 0; py < TILE_HEIGHT; py++) {
for (int px = 0; px < TILE_WIDTH; px++) {
// Calculate the distance between the current position and the rotation angle
dx = px - ROTATION_CENTER_X;
dy = py - ROTATION_CENTER_Y;
distance = Math.sqrt(dx * dx + dy * dy);
// Avoid division by zero
if (distance == 0) {
distance = 1;
}
// 0 degree on top
angle = Math.abs(Math.toDegrees(Math.acos(dx / distance)));
if (dx >= 0 && dy <= 0) {
angle = 90.0 - angle;
} else if (dx >= 0 && dy >= 0) {
angle += 90.0;
} else if (dx <= 0 && dy >= 0) {
angle += 90.0;
} else if (dx <= 0 && dy <= 0) {
angle = 450.0 - angle;
}
// Check for each angle in fractionAngles array
for (int i = 0; i < (MAX - 1); i++) {
if ((angle >= FRACTION_ANGLES[i])) {
currentRed = COLORS[i].getRed() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * RED_STEP_LOOKUP[i];
currentGreen = COLORS[i].getGreen() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * GREEN_STEP_LOOKUP[i];
currentBlue = COLORS[i].getBlue() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * BLUE_STEP_LOOKUP[i];
currentAlpha = COLORS[i].getAlpha() * INT_TO_FLOAT_CONST + (angle - FRACTION_ANGLES[i]) * ALPHA_STEP_LOOKUP[i];
continue;
}
}
// Fill data array with calculated color values
final int BASE = (py * TILE_WIDTH + px) * 4;
data[BASE + 0] = (int) (currentRed * 255);
data[BASE + 1] = (int) (currentGreen * 255);
data[BASE + 2] = (int) (currentBlue * 255);
data[BASE + 3] = (int) (currentAlpha * 255);
}
}
// Fill the raster with the data
RASTER.setPixels(0, 0, TILE_WIDTH, TILE_HEIGHT, data);
return RASTER;
}
}
}
}
To try and do what you're doing with a pie slices, you need to slow alter the color from one point to another, using very small slices. So, while you might start with Color.RED and want to blend to Color.YELLOW, you would actually need to generate each color between them (based on the distance)
For example...
import core.ui.ColorUtilities;
import static core.ui.ColorUtilities.blend;
import static core.ui.ColorUtilities.getFractionIndicies;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ColorWheel {
public static void main(String[] args) {
new ColorWheel();
}
public ColorWheel() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
float fractions[] = new float[]{0, 0.16f, 0.33f, 0.5f, 0.66f, 0.83f, 1f};
Color colors[] = new Color[]{Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.RED};
for (int index = 0; index < getWidth(); index++) {
float progress = (float) index / getWidth();
System.out.println(progress);
Color color = blendColors(fractions, colors, progress);
g2d.setColor(color);
g2d.drawLine(index, 0, index, getHeight());
}
g2d.dispose();
}
}
/**
* This will attempt to blend two colors that site between the supplied
* progress value, based on the distance of the progress value...
*
* For example, if you have a series of fractions of {0f, 0.5f, 1f} and a
* progress of 25%, the resulting color will be a 50% blend of the first and
* second color (as the progress is half between those two points)
*
* #param fractions
* #param colors
* #param progress
* #return
*/
public static Color blendColors(float[] fractions, Color[] colors, float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(fractions, progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
}
I know, it's note a wheel, but i was demonstrating the blending algorithm, I'll leave the pie slicing up to you ;)
Just proving to myself it could work...
int dimeter = Math.min(getWidth(), getHeight());
int x = (getWidth() - dimeter) / 2;
int y = (getHeight() - dimeter) / 2;
for (int angle = 0; angle < 360; angle++) {
float progress = (float) angle / 360;
System.out.println(progress);
Color color = blendColors(fractions, colors, progress);
g2d.setColor(color);
g2d.fillArc(x, y, dimeter, dimeter, angle + 90, 2);
}

Printing a 1800 x 1200 image on 4 x 6 paper using Java

I need to print a 1800 x 1200 pixels, 300 dpi image on 4" x 6" paper (also known as 4r)
What I have Tried
I have created a PrintRequestAttributeSet which takes care of my PrintableArea(4 x 6), Printer print DPI, Orientation. I have attached a MCVE at the bottom.
Problem
While the code works, and I get a PageFormat with the following attributes(for my printer) :
x= 12.0
y= 12.32
w= 276.0
h= 419.67
The width and height are little less, because my printer doesn't support Zero Margin. (This is what I have considered. If anyone is aware of a way other than this through which I can force zero margin, please let me know)
I am supplying the margin as 0, because these images will be printed via printers which support zero margin(Photobooth Printers).
aset.add(new MediaPrintableArea(0, 0, 4, 6, MediaPrintableArea.INCH));
The printable area including the margin is roughly 4 x 6 as required. The problem occurs when I scale the image to print inside the printable area.
Since image is 1800 x 1200, it supports an aspect ratio of 3:2, which means the image is created to get printed on a 4 x 6 paper(after getting rotated and scaled). For Reference.
Now, since the pageWidth and pageHeight of the PageFormat are not exactly divisible by the ImageWidth and ImageHeight. I am getting scaling issues.
Note : I rotate the image because it has to be printed on 4 x 6 and not 6 x 4.
The image which is supposed to take 4 x 6 space is taking somewhere close to 4 x 5. The image size is also reduced drastically.
How do I overcome this issue?
Code
Please find the MCVE here :
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.standard.PrinterResolution;
public class ImgPrinter implements Printable {
Image img;
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
Graphics2D g2d = (Graphics2D) graphics;
g2d.translate((int) (pageFormat.getImageableX()),
(int) (pageFormat.getImageableY()));
if (pageIndex == 0) {
double pageWidth = pageFormat.getImageableWidth();
double pageHeight = pageFormat.getImageableHeight();
/**
* Swapping width and height, coz the image is later rotated
*/
double imageWidth = img.getHeight(null);
double imageHeight = img.getWidth(null);
double scaleX = pageWidth / imageWidth;
double scaleY = pageHeight / imageHeight;
g2d.scale(scaleX, scaleY);
g2d.rotate(Math.toRadians(90), img.getWidth(null) / 2,
img.getHeight(null) / 2);
g2d.drawImage(img, 0, 0, null);
return Printable.PAGE_EXISTS;
}
return Printable.NO_SUCH_PAGE;
}
public void printPage(String file, String size) {
try {
Image img = ImageIO.read(new File(file));
this.img = img;
PrintRequestAttributeSet aset = createAsetForMedia(size);
PrinterJob pj = PrinterJob.getPrinterJob();
PageFormat pageFormat = pj.getPageFormat(aset);
pj.setPrintable(this, pageFormat);
pj.print();
} catch (PrinterException ex) {
ex.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private PrintRequestAttributeSet createAsetForMedia(String size) {
PrintRequestAttributeSet aset = null;
try {
aset = new HashPrintRequestAttributeSet();
aset.add(PrintQuality.NORMAL);
aset.add(OrientationRequested.PORTRAIT);
/**
* Suggesting the print DPI as 300
*/
aset.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
/**
* Setting the printable area and the margin as 0
*/
if (size.equals("3r")) {
aset.add(new MediaPrintableArea(0, 0, 3, 5,
MediaPrintableArea.INCH));
} else if (size.equals("4r")) {
aset.add(new MediaPrintableArea(0, 0, 4, 6,
MediaPrintableArea.INCH));
} else if (size.equals("5r")) {
aset.add(new MediaPrintableArea(0, 0, 5, 7,
MediaPrintableArea.INCH));
} else if (size.equals("6r")) {
aset.add(new MediaPrintableArea(0, 0, 6, 8,
MediaPrintableArea.INCH));
}
} catch (Exception e) {
e.printStackTrace();
}
return aset;
}
public static void main(String[] args) {
new ImgPrinter().printPage("/Some_URL/sam.jpg",
"4r");
}
}
To run the program, just supply a 1800x1200 image path to the main program and it will print to the default printer.
Things that worry me...
Changing the scale/rotation of the Graphics context without either first making a copy of it or resetting it after the fact. This could actually affect subsequent renderings, as the printable may be called multiple times...
Using Graphics2D#scale. This really isn't the best quality nor is it generally that fast. See The Perils of Image.getScaledInstance(). I also prefer to use AffineTransform, but that's just me...
Not buffering the result. Okay, this relates to the previous comment, but your print method may be called multiple times to print a single page, scaling the image each time is costly, instead, you should scale it once and re-use the scaled result.
Unless you're going to physically rotate the image, you probably want to rotate about the center of the page, not the image itself, this will affect the location where 0x0 becomes.
Now remember, when you rotate the Graphics context, the origin point changes, so instead of been in the top/left corner, in this case, it will become the top/right corner. And now you know why I would have rotated the image in isolation and not tried messing around with the Graphics context :P
What I "think" is happening is that between the scaling, rotating and manipulations of the coordinates (swapping the height and width), things are getting screwed...but frankly, I wasn't going to mess around with it when I have better solutions...
The following example makes use of a bunch of personal library code, so some of might be a little convoluted, but I use the separate functionality for other things, so it binds well together...
So, starting with an image of 7680x4800, this generates a scaled image of 423x264
(the red border are visual guides only, used when dumping the result to PDF to save paper ;))
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.PrintQuality;
import javax.print.attribute.standard.PrinterResolution;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ImgPrinter implements Printable {
BufferedImage img;
BufferedImage scaled;
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
int result = NO_SUCH_PAGE;
Graphics2D g2d = (Graphics2D) graphics.create();
g2d.translate((int) (pageFormat.getImageableX()), (int) (pageFormat.getImageableY()));
if (pageIndex == 0) {
double pageWidth = pageFormat.getImageableWidth();
double pageHeight = pageFormat.getImageableHeight();
if (scaled == null) {
// Swap the width and height to allow for the rotation...
System.out.println(pageWidth + "x" + pageHeight);
scaled = getScaledInstanceToFit(
img,
new Dimension((int)pageHeight, (int)pageWidth));
System.out.println("In " + img.getWidth() + "x" + img.getHeight());
System.out.println("Out " + scaled.getWidth() + "x" + scaled.getHeight());
}
double imageWidth = scaled.getWidth();
double imageHeight = scaled.getHeight();
AffineTransform at = AffineTransform.getRotateInstance(
Math.toRadians(90),
pageWidth / 2d,
pageHeight / 2d
);
AffineTransform old = g2d.getTransform();
g2d.setTransform(at);
double x = (pageHeight - imageWidth) / 2d;
double y = (pageWidth - imageHeight) / 2d;
g2d.drawImage(
scaled,
(int)x,
(int)y,
null);
g2d.setTransform(old);
// This is not affected by the previous changes, as those were made
// to a different copy...
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, (int)pageWidth - 1, (int)pageHeight - 1);
result = PAGE_EXISTS;
}
g2d.dispose();
return result;
}
public void printPage(String file, String size) {
try {
img = ImageIO.read(new File(file));
PrintRequestAttributeSet aset = createAsetForMedia(size);
PrinterJob pj = PrinterJob.getPrinterJob();
PageFormat pageFormat = pj.getPageFormat(aset);
pj.setPrintable(this, pageFormat);
if (pj.printDialog()) {
pj.print();
}
} catch (PrinterException ex) {
ex.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private PrintRequestAttributeSet createAsetForMedia(String size) {
PrintRequestAttributeSet aset = null;
try {
aset = new HashPrintRequestAttributeSet();
aset.add(PrintQuality.NORMAL);
aset.add(OrientationRequested.PORTRAIT);
/**
* Suggesting the print DPI as 300
*/
aset.add(new PrinterResolution(300, 300, PrinterResolution.DPI));
/**
* Setting the printable area and the margin as 0
*/
if (size.equals("3r")) {
aset.add(new MediaPrintableArea(1, 1, 3, 5,
MediaPrintableArea.INCH));
} else if (size.equals("4r")) {
aset.add(new MediaPrintableArea(1, 1, 4, 6,
MediaPrintableArea.INCH));
} else if (size.equals("5r")) {
aset.add(new MediaPrintableArea(1, 1, 5, 7,
MediaPrintableArea.INCH));
} else if (size.equals("6r")) {
aset.add(new MediaPrintableArea(1, 1, 6, 8,
MediaPrintableArea.INCH));
}
} catch (Exception e) {
e.printStackTrace();
}
return aset;
}
public static BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
double scaleFactor = getScaleFactorToFit(img, size);
return getScaledInstance(img, scaleFactor);
}
public static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
return getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
public static double getScaleFactorToFit(BufferedImage img, Dimension size) {
double dScale = 1;
if (img != null) {
int imageWidth = img.getWidth();
int imageHeight = img.getHeight();
dScale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
}
return dScale;
}
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
return (double) iTargetSize / (double) iMasterSize;
}
protected static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint) {
BufferedImage imgScale = img;
int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);
if (dScaleFactor <= 1.0d) {
imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight, hint);
} else {
imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight, hint);
}
return imgScale;
}
protected static BufferedImage getScaledDownInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint) {
// System.out.println("Scale down...");
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w = img.getWidth();
int h = img.getHeight();
do {
if (w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
protected static BufferedImage getScaledUpInstance(BufferedImage img,
int targetWidth,
int targetHeight,
Object hint) {
int type = BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w = img.getWidth();
int h = img.getHeight();
do {
if (w < targetWidth) {
w *= 2;
if (w > targetWidth) {
w = targetWidth;
}
}
if (h < targetHeight) {
h *= 2;
if (h > targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
tmp = null;
} while (w != targetWidth || h != targetHeight);
return ret;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new ImgPrinter().printPage("/Volumes/Disk02/Dropbox/Wallpapers/animepaper.net_wallpaper_art_anime_aria_duanwu_festival_205050_wonderngo_7680x4800-a8aecc9c.jpg",
"4r");
}
});
}
}
You know what would MUCH easier, printing the page in landscape mode to start with :P
I would say you need proportional scaling. Like this
double scaleX = pageWidth / imageWidth;
double scaleY = pageHeight / imageHeight;
double scale = Math.min(scaleX, scaleY);
g2d.scale(scale, scale);
UPDATE:
One more suggestions as mKorbel mentioned would be separate scaling.
Try use public Image getScaledInstance(int width, int height, int hints) method of BufferedImage
passing Image.SCALE_SMOOTH as the hint.

Explanation for the bulge effect algorithm

I am a beginner at Java, only been coding for a year. My task is to create distortions to any image given. I have been having a lot of trouble with the bulge effect lately. I have been researching all around google and I found these links very helpful:
https://math.stackexchange.com/questions/266250/explanation-of-this-image-warping-bulge-filter-algorithm
Image Warping - Bulge Effect Algorithm
I tried the algorithm that these two links gave me, but I ended up with nothing.
Let's say I have imported an image that is 100 pixels by 100 pixels, from the code below, am I using the algorithm correctly:
//modifiedImage is a global variable and contains the image that is 100x100
public BufferedImage buldge(){
double X = 0;
double Y = 0;
BufferedImage anImage = new BufferedImage (1000, 1000, BufferedImage.TYPE_INT_ARGB);
for(int x = 0; x < modifiedImage.getWidth(); x++){
for(int y = 0; y < modifiedImage.getHeight(); y++){
int rgb = modifiedImage.getRGB(x, y);
double newRadius = 0;
X = x - x/2;
Y = y - y/2;
double radius = Math.sqrt(X*X + Y*Y);
double angle = Math.atan2(X, Y);
newRadius = Math.pow(radius,1.5);
X = (int)(newRadius*Math.sin(angle));
Y = (int)(newRadius*Math.cos(angle));
anImage.setRGB((int)X, (int)Y,rgb);
}
}
return anImage;
}
The problem is that this code doesn't really bulge the image in the middle. I have made a new BufferedImage of dimensions 1000x1000 because the pixels from the original one gets extended really far and some are extended beyond 1000x1000. If anyone would help me show the problems in this code concerning the bulge effect, I would greatly appreciate it.
I think one (main) part of the problem is that you are computing the radius of the bulge effect in pixels. Although I have not read all anwers in the threads that you linked, it seems like they are referring to texture coordinates - that is, to values between 0 and 1.
Apart from that: With the current approach, you will have a sampling problem. Imagine that one pixel at the center of the input image will be "stretched" so that it covers an area of, say, 10x10 pixels in the output image. But still, you are only computing one new position for this pixel.
Imagine it like you are taking pixels from the input image, and move them to a new position in the output image - but you have to do it the other way around: You have to check each pixel in the output image, and compute which pixel of the input image was moved there.
I created a small example: It allows moving a "magnifying glass" over the image with the mouse. With the mouse wheel, you can change the strength of the distortion. With SHIFT+MouseWheel, you can change the size of the magnifying glass.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.beans.Transient;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ImageBulgeTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridLayout(1, 1));
frame.getContentPane().add(new ImageBulgePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class ImageBulgePanel extends JPanel
{
private BufferedImage input;
private BufferedImage output;
private double bulgeStrength = 0.3;
private double bulgeRadius = 100;
ImageBulgePanel()
{
try
{
input = ImageIO.read(new File("lena512color.png"));
}
catch (IOException e1)
{
e1.printStackTrace();
}
addMouseMotionListener(new MouseAdapter()
{
#Override
public void mouseMoved(MouseEvent e)
{
updateImage(e.getX(), e.getY());
}
});
addMouseWheelListener(new MouseWheelListener()
{
#Override
public void mouseWheelMoved(MouseWheelEvent e)
{
if ((e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) ==
InputEvent.SHIFT_DOWN_MASK)
{
bulgeRadius += 10 * e.getWheelRotation();
System.out.println("bulgeRadius "+bulgeRadius);
}
else
{
bulgeStrength += 0.1 * e.getWheelRotation();
bulgeStrength = Math.max(0, bulgeStrength);
System.out.println("bulgeStrength "+bulgeStrength);
}
updateImage(e.getX(), e.getY());
}
});
}
#Override
#Transient
public Dimension getPreferredSize()
{
if (isPreferredSizeSet())
{
return super.getPreferredSize();
}
return new Dimension(input.getWidth(), input.getHeight());
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
if (output != null)
{
g.drawImage(output, 0, 0, null);
}
}
private void updateImage(int x, int y)
{
if (output == null)
{
output = new BufferedImage(
input.getWidth(), input.getHeight(),
BufferedImage.TYPE_INT_ARGB);
}
computeBulgeImage(input, x, y,
bulgeStrength, bulgeRadius,
output);
repaint();
}
private static void computeBulgeImage(
BufferedImage input, int cx, int cy,
double bulgeStrength, double bulgeRadius,
BufferedImage output)
{
int w = input.getWidth();
int h = input.getHeight();
for(int x = 0; x < w; x++)
{
for(int y = 0; y < h; y++)
{
int dx = x-cx;
int dy = y-cy;
double distanceSquared = dx * dx + dy * dy;;
int sx = x;
int sy = y;
if (distanceSquared < bulgeRadius * bulgeRadius)
{
double distance = Math.sqrt(distanceSquared);
boolean otherMethod = false;
otherMethod = true;
if (otherMethod)
{
double r = distance / bulgeRadius;
double a = Math.atan2(dy, dx);
double rn = Math.pow(r, bulgeStrength)*distance;
double newX = rn*Math.cos(a) + cx;
double newY = rn*Math.sin(a) + cy;
sx += (newX - x);
sy += (newY - y);
}
else
{
double dirX = dx / distance;
double dirY = dy / distance;
double alpha = distance / bulgeRadius;
double distortionFactor =
distance * Math.pow(1-alpha, 1.0 / bulgeStrength);
sx -= distortionFactor * dirX;
sy -= distortionFactor * dirY;
}
}
if (sx >= 0 && sx < w && sy >= 0 && sy < h)
{
int rgb = input.getRGB(sx, sy);
output.setRGB(x, y, rgb);
}
}
}
}
}

Categories

Resources