/** * RasterPlotter * Copyright 2005 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany * First released 16.09.2005 at http://yacy.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in the file lgpl21.txt * If not, see <http://www.gnu.org/licenses/>. */ package net.yacy.visualization; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Set; import java.util.TreeSet; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import net.yacy.cora.util.ByteBuffer; import net.yacy.cora.util.ConcurrentLog; /** * This Class implements some convenience-methods to support drawing of statistical Data * It is not intended to replace existing awt-funktions even if it looks so * This class provides some drawing methods that creates transparency effects that * are not available in awt. */ public class RasterPlotter { public static final ConcurrentLog log = new ConcurrentLog("RasterPlotter"); public static final double PI180 = Math.PI / 180.0d; public static final double PI4 = Math.PI / 4.0d; public static final double PI2 = Math.PI / 2.0d; public static final double PI32 = PI2 * 3.0d; public static final double TL = Math.sqrt(3) / 2; // colors regarding RGB Color Model public static final long RED = 0xFF0000; public static final long GREEN = 0x00FF00; public static final long BLUE = 0x0000FF; public static final long GREY = 0x888888; public static enum DrawMode { MODE_REPLACE, MODE_ADD, MODE_SUB; } public static enum FilterMode { FILTER_ANTIALIASING, FILTER_BLUR, FILTER_INVERT; } protected final int width, height; private final int[] cc; private BufferedImage image; private WritableRaster grid; private int defaultColR, defaultColG, defaultColB; private final long backgroundCol; private DrawMode defaultMode; private byte[] frame; public RasterPlotter(final int width, final int height, final DrawMode drawMode, final String backgroundColor) { this(width, height, drawMode, Long.parseLong(backgroundColor, 16)); } public RasterPlotter(final int width, final int height, final DrawMode drawMode, final long backgroundColor) { this.cc = new int[3]; this.width = width; this.height = height; this.backgroundCol = backgroundColor; this.defaultColR = 0xFF; this.defaultColG = 0xFF; this.defaultColB = 0xFF; this.defaultMode = drawMode; try { // we need our own frame buffer to get a very, very fast transformation to png because we can omit the PixedGrabber, which is up to 800 times slower // see: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4835595 this.frame = new byte[width * height * 3]; DataBuffer videoBuffer = new DataBufferByte(frame, frame.length); ComponentSampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 3, width * 3, new int[] {0, 1, 2}); this.grid = Raster.createWritableRaster(sampleModel, videoBuffer, null); ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), null, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); this.image = new BufferedImage(colorModel, this.grid, false, null); } catch (final OutOfMemoryError e) { this.frame = null; try { this.image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED); } catch (final OutOfMemoryError ee) { try { this.image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY); } catch (final OutOfMemoryError eee) { this.image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY); } } this.grid = this.image.getRaster(); } clear(); } /** * Deletes all pixels of image and sets them to previously defined * background color. */ public final void clear() { // fill grid with background color final int bgR = (int) (this.backgroundCol >> 16); final int bgG = (int) ((this.backgroundCol >> 8) & 0xff); final int bgB = (int) (this.backgroundCol & 0xff); if (this.frame == null) { final Graphics2D gr = this.image.createGraphics(); Color c = new Color(bgR, bgG, bgB); gr.setBackground(c); gr.clearRect(0, 0, this.width, this.height); gr.setColor(c); gr.fillRect(0, 0, this.width, this.height); } else { int p = 0; for (int i = 0; i < width; i++) { this.frame[p++] = (byte) bgR; this.frame[p++] = (byte) bgG; this.frame[p++] = (byte) bgB; } final int rw = width * 3; for (int i = 1; i < height; i++) { System.arraycopy(this.frame, 0, this.frame, i * rw, rw); } } } public void setDrawMode(final DrawMode drawMode) { this.defaultMode = drawMode; } public BufferedImage getImage() { return this.image; } public int getWidth() { return this.width; } public int getHeight() { return this.height; } public static boolean darkColor(final String s) { return darkColor(Long.parseLong(s, 16)); } public static boolean darkColor(final long c) { final int r = (int) (c >> 16); final int g = (int) ((c >> 8) & 0xff); final int b = (int) (c & 0xff); return r + g + b < 384; } public int[] getPixel(final int x, final int y) { return getPixel(x, y, new int[3]); } public int[] getPixel(final int x, final int y, int[] c) { if (this.frame == null) return this.grid.getPixel(x, y, c); int cell = (this.width * y + x) * 3; c[0] = this.frame[cell++] & 0xff; c[1] = this.frame[cell++] & 0xff; c[2] = this.frame[cell++] & 0xff; return c; } public void setPixel(final int x, final int y, int[] c) { if (this.frame == null) { this.grid.setPixel(x, y, c); return; } int cell = (this.width * y + x) * 3; this.frame[cell++] = (byte) c[0]; this.frame[cell++] = (byte) c[1]; this.frame[cell++] = (byte) c[2]; } public void setColor(final long c) { if (this.defaultMode == DrawMode.MODE_SUB) { final int r = (int) (c >> 16); final int g = (int) ((c >> 8) & 0xff); final int b = (int) (c & 0xff); this.defaultColR = (g + b) >>> 1; // / 2; this.defaultColG = (r + b) >>> 1; // / 2; this.defaultColB = (r + g) >>> 1; // / 2; } else { this.defaultColR = (int) (c >> 16); this.defaultColG = (int) ((c >> 8) & 0xff); this.defaultColB = (int) (c & 0xff); } } public void plot(final int x, final int y) { plot(x, y, 100); } public void plot(final int x, final int y, final int intensity) { if ((x < 0) || (x >= this.width)) return; if ((y < 0) || (y >= this.height)) return; try { if (this.defaultMode == DrawMode.MODE_REPLACE) { if (intensity == 100) synchronized (this.cc) { this.cc[0] = this.defaultColR; this.cc[1] = this.defaultColG; this.cc[2] = this.defaultColB; setPixel(x, y, this.cc); } else synchronized (this.cc) { final int[] c = getPixel(x, y, this.cc); c[0] = (intensity * this.defaultColR + (100 - intensity) * c[0]) / 100; c[1] = (intensity * this.defaultColG + (100 - intensity) * c[1]) / 100; c[2] = (intensity * this.defaultColB + (100 - intensity) * c[2]) / 100; setPixel(x, y, c); } } else if (this.defaultMode == DrawMode.MODE_ADD) synchronized (this.cc) { int[] c = null; try { c = getPixel(x, y, this.cc); } catch (final ArrayIndexOutOfBoundsException e) { // catch "Coordinate out of bounds" return; } if (intensity == 100) { c[0] = (0xff & c[0]) + this.defaultColR; if (this.cc[0] > 255) this.cc[0] = 255; c[1] = (0xff & c[1]) + this.defaultColG; if (this.cc[1] > 255) this.cc[1] = 255; c[2] = (0xff & c[2]) + this.defaultColB; if (this.cc[2] > 255) this.cc[2] = 255; } else { c[0] = (0xff & c[0]) + (intensity * this.defaultColR / 100); if (this.cc[0] > 255) this.cc[0] = 255; c[1] = (0xff & c[1]) + (intensity * this.defaultColG / 100); if (this.cc[1] > 255) this.cc[1] = 255; c[2] = (0xff & c[2]) + (intensity * this.defaultColB / 100); if (this.cc[2] > 255) this.cc[2] = 255; } setPixel(x, y, c); } else if (this.defaultMode == DrawMode.MODE_SUB) synchronized (this.cc) { int[] c = null; try { c = getPixel(x, y, this.cc); } catch (final ArrayIndexOutOfBoundsException e) { // catch "Coordinate out of bounds" return; } if (intensity == 100) { c[0] = (0xff & c[0]) - this.defaultColR; if (this.cc[0] < 0) this.cc[0] = 0; c[1] = (0xff & c[1]) - this.defaultColG; if (this.cc[1] < 0) this.cc[1] = 0; c[2] = (0xff & c[2]) - this.defaultColB; if (this.cc[2] < 0) this.cc[2] = 0; } else { c[0] = (0xff & c[0]) - (intensity * this.defaultColR / 100); if (this.cc[0] < 0) this.cc[0] = 0; c[1] = (0xff & c[1]) - (intensity * this.defaultColG / 100); if (this.cc[1] < 0) this.cc[1] = 0; c[2] = (0xff & c[2]) - (intensity * this.defaultColB / 100); if (this.cc[2] < 0) this.cc[2] = 0; } setPixel(x, y, c); } } catch (final ArrayIndexOutOfBoundsException e) { log.warn(e.getMessage() + ": x = " + x + ", y = " + y); } // may appear when pixel coordinate is out of bounds } public void line(final int Ax, final int Ay, final int Bx, final int By, final int intensity) { line(Ax, Ay, Bx, By, null, intensity, null, -1, -1, -1, -1, false); } /** * draw a line using Bresenham's line drawing algorithm. * The line will be plotted together with dots on it, if wanted. * @param Ax * @param Ay * @param Bx * @param By * @param colorLine * @param intensityLine * @param colorDot * @param intensityDot * @param dotDist * @param dotPos * @param dotRadius * @param dotFilled */ public void line( int Ax, int Ay, final int Bx, final int By, final Long colorLine, final int intensityLine, final Long colorDot, final int intensityDot, final int dotDist, final int dotPos, final int dotRadius, final boolean dotFilled ) { // Bresenham's line drawing algorithm int dX = Math.abs(Bx-Ax); int dY = Math.abs(By-Ay); final int Xincr = (Ax > Bx) ? -1 : 1; final int Yincr = (Ay > By) ? -1 : 1; int dotc = 0; if (dX >= dY) { final int dPr = dY<<1; final int dPru = dPr - (dX<<1); int P = dPr - dX; for (; dX >= 0; dX--) { if (colorLine != null) this.setColor(colorLine); plot(Ax, Ay, intensityLine); if (dotc == dotPos) { if (colorDot != null) this.setColor(colorDot); if (dotRadius == 0) this.plot(Ax, Ay, intensityDot); else if (dotRadius > 0) dot(Ax, Ay, dotRadius, dotFilled, intensityDot); } dotc++; if (dotc == dotDist) dotc = 0; if (P > 0) { Ax += Xincr; Ay += Yincr; P += dPru; } else { Ax += Xincr; P += dPr; } } } else { final int dPr = dX<<1; final int dPru = dPr - (dY<<1); int P = dPr - dY; for (; dY >= 0; dY--) { if (colorLine != null) this.setColor(colorLine); plot(Ax, Ay, intensityLine); if (dotc == dotPos) { if (colorDot != null) this.setColor(colorDot); if (dotRadius == 0) this.plot(Ax, Ay, intensityDot); else if (dotRadius > 0) dot(Ax, Ay, dotRadius, dotFilled, intensityDot); } dotc++; if (dotc == dotDist) dotc = 0; if (P > 0) { Ax += Xincr; Ay += Yincr; P += dPru; } else { Ay += Yincr; P += dPr; } } } } /** * draw a line with a dot at the end * @param x0 start point * @param y0 start point * @param x1 end point * @param y1 end point * @param radius radius of the dot * @param padding the distance of the dot border to the end point * @param lineColor the color of the line * @param dotColor the color of the dot */ public void lineDot(final int x0, final int y0, final int x1, final int y1, final int radius, final int padding, final long lineColor, final long dotColor) { final double dx = x1 - x0; // distance of points, x component final double dy = y1 - y0; // distance of points, y component final double angle = Math.atan2(dy, dx); // the angle of the line between the points final double d = Math.sqrt((dx * dx + dy * dy)); // the distance between the points (Pythagoras) final double ddotcenter = d - radius - padding; // distance from {x0, y0} to dot center near {x1, y1} final double ddotborder = ddotcenter - radius; // distance to point {x3, y3} at border of dot center at {x2, y2} final double xn = Math.cos(angle); // normalized vector component x final double yn = Math.sin(angle); // normalized vector component y final int x2 = x0 + ((int) (ddotcenter * xn)); // dot center, x component final int y2 = y0 + ((int) (ddotcenter * yn)); // dot center, y component final int x3 = x0 + ((int) (ddotborder * xn)); // dot border, x component final int y3 = y0 + ((int) (ddotborder * yn)); // dot border, y component setColor(lineColor); line(x0, y0, x3, y3, 100); // draw line from {x0, y0} to dot border setColor(dotColor); dot(x2, y2, radius, true, 100); // draw dot at {x2, y2} } /** * draw a line with an arrow at the end * @param x0 start point * @param y0 start point * @param x1 end point * @param y1 end point * @param sidelength the side length of the arrow tip (all 3 sides are equal) * @param padding the distance of the arrow tip to the end point * @param lineColor the color of the line * @param arrowColor the color of the arrow tip */ public void lineArrow(final int x0, final int y0, final int x1, final int y1, final int sidelength, final int padding, final long lineColor, final long arrowColor) { final double dx = x1 - x0; // distance of points, x component final double dy = y1 - y0; // distance of points, y component final double angle = Math.atan2(dy, dx); // the angle of the line between the points final double d = Math.sqrt((dx * dx + dy * dy)); // the distance between the points (Pythagoras) final double arrowtip = d - padding; // the distance from {x0, y0} to the arrow tip final double arrowlength = TL * sidelength; // the length of the arrow (distance from base to tip) final double arrowbase = arrowtip - arrowlength; // the distance from {x0, y0} to the arrow base final double xn = Math.cos(angle); // normalized vector component x final double yn = Math.sin(angle); // normalized vector component y final int xt = x0 + ((int) (arrowtip * xn)); // arrow tip point component x final int yt = y0 + ((int) (arrowtip * yn)); // arrow tip point component y final double xb = x0 + arrowbase * xn; // arrow base point component x final double yb = y0 + arrowbase * yn; // arrow base point component y final double sl2 = sidelength / 2.0; // half of the side length final double xk = sl2 * Math.cos(angle + PI2); // point at 90 degree on arrow direction to left side, vector component x final double yk = sl2 * Math.sin(angle + PI2); // point at 90 degree on arrow direction to left side, vector component y final int x2 = (int) (xb + xk); final int y2 = (int) (yb + yk); final int x3 = (int) (xb - xk); final int y3 = (int) (yb - yk); setColor(lineColor); line(x0, y0, (int) xb, (int) yb, 100); // draw line from {x0, y0} to arrow base setColor(arrowColor); line(x2, y2, x3, y3, 100); // base line line(x2, y2, xt, yt, 100); // left line line(x3, y3, xt, yt, 100); // right line } public void dot(final int x, final int y, final int radius, final boolean filled, final int intensity) { if (filled) { for (int r = radius; r >= 0; r--) { CircleTool.circle(this, x, y, r, intensity); } } else { CircleTool.circle(this, x, y, radius, intensity); } } public void arc(final int x, final int y, final int innerRadius, final int outerRadius, final int intensity) { for (int r = innerRadius; r <= outerRadius; r++) { CircleTool.circle(this, x, y, r, intensity); } } public void arc(final int x, final int y, final int innerRadius, final int outerRadius, final int fromArc, final int toArc) { for (int r = innerRadius; r <= outerRadius; r++) { CircleTool.circle(this, x, y, r, fromArc, toArc); } } /** * draw a portion of a line from the center of a circle * @param cx center of circle, x * @param cy center of circle, y * @param innerRadius inner radius of line * @param outerRadius outer radius of line * @param angle angle within the circle for the line * @param in direction, if true: inward. This is the moving direction of dots, if dotRadius is alternated from 0 to 360 * @param colorLine the color of the line * @param colorDot the color of the dot * @param dotDist the distance of two dots * @param dotPos the start position of the first dot * @param dotRadius the radius of the dot * @param dotFilled if true: dot is filled. */ public void arcLine(final int cx, final int cy, final int innerRadius, final int outerRadius, final double angle, final boolean in, final Long colorLine, final Long colorDot, final int dotDist, final int dotPos, final int dotRadius, final boolean dotFilled) { final double a = PI180 * angle; final double cosa = Math.cos(a); final double sina = Math.sin(a); final int xi = cx + (int) (innerRadius * cosa); final int yi = cy - (int) (innerRadius * sina); final int xo = cx + (int) (outerRadius * cosa); final int yo = cy - (int) (outerRadius * sina); //line(xi, yi, xo, yo, 100); if (in) { line( xo, yo, xi, yi, colorLine, 100, colorDot, 100, dotDist, dotPos, dotRadius, dotFilled ); } else { line( xi, yi, xo, yo, colorLine, 100, colorDot, 100, dotDist, dotPos, dotRadius, dotFilled ); } } public void arcDot(final int cx, final int cy, final int arcRadius, final double angle, final int dotRadius) { final double a = PI180 * angle; final int x = cx + (int) (arcRadius * Math.cos(a)); final int y = cy - (int) (arcRadius * Math.sin(a)); dot(x, y, dotRadius, true, 100); } /** * draw a connecting line between two points on a circle * @param cx center of circle, x * @param cy center of circle, y * @param arcRadius radius of circle * @param angle1 position of dot 1 on circle * @param angle2 position of dot 2 on circle * @param in direction of dots on line; in=true means: inwards * @param colorLine * @param intensityLine * @param colorDot * @param intensityDot * @param dotDist * @param dotPos * @param dotRadius * @param dotFilled */ public void arcConnect(final int cx, final int cy, final int arcRadius, final double angle1, final double angle2, final boolean in, final Long colorLine, final int intensityLine, final Long colorDot, final int intensityDot, final int dotDist, final int dotPos, final int dotRadius, final boolean dotFilled, final String message, final Long colorMessage, final int intensityMessage) { final double a1 = PI180 * angle1; final double a2 = PI180 * angle2; // find positions of points final int x1 = cx + (int) (arcRadius * Math.cos(a1)); final int y1 = cy - (int) (arcRadius * Math.sin(a1)); final int x2 = cx + (int) (arcRadius * Math.cos(a2)); final int y2 = cy - (int) (arcRadius * Math.sin(a2)); // draw the line if (in) { line(x1, y1, x2, y2, colorLine, intensityLine, colorDot, intensityDot, dotDist, dotPos, dotRadius, dotFilled); } else { line(x2, y2, x1, y1, colorLine, intensityLine, colorDot, intensityDot, dotDist, dotPos, dotRadius, dotFilled); } // draw a name on the line if (message != null && message.length() > 0) { this.setColor(colorMessage); int xm = (x1 + 5 * x2) / 6; int ym = (y1 + 5 * y2) / 6; if (ym < cy) ym += 6; else ym -=6; if (xm < cx) xm += 6; else xm -=6; if (xm > cx) xm -= 6 * message.length(); PrintTool.print(this, xm, ym, 0, message.toUpperCase(), -1, intensityMessage); } } public void arcArc(final int cx, final int cy, final int arcRadius, final double angle, final int innerRadius, final int outerRadius, final int intensity) { final double a = PI180 * angle; final int x = cx + (int) (arcRadius * Math.cos(a)); final int y = cy - (int) (arcRadius * Math.sin(a)); arc(x, y, innerRadius, outerRadius, intensity); } public void arcArc(final int cx, final int cy, final int arcRadius, final double angle, final int innerRadius, final int outerRadius, final int fromArc, final int toArc) { final double a = PI180 * angle; final int x = cx + (int) (arcRadius * Math.cos(a)); final int y = cy - (int) (arcRadius * Math.sin(a)); arc(x, y, innerRadius, outerRadius, fromArc, toArc); } /** * inserts image * @param bitmap bitmap to be inserted * @param x x-value of upper left coordinate where bitmap will be placed * @param y y-value of upper left coordinate where bitmap will be placed */ public void insertBitmap(final BufferedImage bitmap, final int x, final int y) { insertBitmap(bitmap, x, y, -1); } /** * inserts image * @param bitmap bitmap to be inserted * @param x x-value of upper left coordinate where bitmap will be placed * @param y y-value of upper left coordinate where bitmap will be placed * @param filter chooses filter */ public void insertBitmap(final BufferedImage bitmap, final int x, final int y, final FilterMode filter) { insertBitmap(bitmap, x, y, -1, filter); } /** * inserts image where all pixels which have the same RGB value as the * pixel at (xx, yy) are transparent * @param bitmap bitmap to be inserted * @param x x-value of upper left coordinate where bitmap will be placed * @param y y-value of upper left coordinate where bitmap will be placed * @param xx x-value of pixel that determines which color is transparent * @param yy y-value of pixel that determines which color is transparent */ public void insertBitmap(final BufferedImage bitmap, final int x, final int y, final int xx, final int yy) { insertBitmap(bitmap, x, y, bitmap.getRGB(xx, yy)); } /** * inserts image where all pixels that have the same RGB value as the * pixel at (xx, yy) are transparent * @param bitmap bitmap to be inserted * @param x x-value of upper left coordinate where bitmap will be placed * @param y y-value of upper left coordinate where bitmap will be placed * @param xx x-value of pixel that determines which color is transparent * @param yy y-value of pixel that determines which color is transparent * @param filter filter to be applied */ public void insertBitmap(final BufferedImage bitmap, final int x, final int y, final int xx, final int yy, final FilterMode filter) { insertBitmap(bitmap, x, y, bitmap.getRGB(xx, yy), filter); } /** * inserts image where all pixels that have the same RGB value as the * pixel at (xx, yy) are transparent * @param bitmap bitmap to be inserted * @param x x-value of upper left coordinate where bitmap will be placed * @param y y-value of upper left coordinate where bitmap will be placed * @param rgb RGB value which will be transparent */ public void insertBitmap(final BufferedImage bitmap, final int x, final int y, final int transRGB) { final int heightSrc = bitmap.getHeight(); final int widthSrc = bitmap.getWidth(); final int heightTgt = this.height; final int widthTgt = this.width; int rgb; for (int i = 0; i < heightSrc; i++) { for (int j = 0; j < widthSrc; j++) { // pixel in legal area? if (j + x >= 0 && i + y >= 0 && i + y < heightTgt && j + x < widthTgt) { rgb = bitmap.getRGB(j, i); if (rgb != transRGB) { this.image.setRGB(j + x, i + y, rgb); } } } } } /** * inserts image where all pixels that have a special RGB value * pixel at (xx, yy) are transparent * @param bitmap bitmap to be inserted * @param x x-value of upper left coordinate where bitmap will be placed * @param y y-value of upper left coordinate where bitmap will be placed * @param rgb RGB value which will be transparent * @param filter filter to be applied */ public void insertBitmap(final BufferedImage bitmap, final int x, final int y, final int transRGB, final FilterMode filter) { insertBitmap(bitmap, x, y, transRGB); final int bitmapWidth = bitmap.getWidth(); final int bitmapHeight = bitmap.getHeight(); if (filter == FilterMode.FILTER_ANTIALIASING) { int transX = -1; int transY = -1; final int imageWidth = this.image.getWidth(); final int imageHeight = this.image.getHeight(); // find first pixel in bitmap that equals transRGB // and also lies in area of image that will be covered by bitmap int i = 0; int j = 0; boolean found = false; while ((i < bitmapWidth) && (i + x < imageWidth) && !found) { while ((j < bitmapHeight) && (j + y < imageHeight) && !found) { if (bitmap.getRGB(i, j) == transRGB) { transX = i; transY = j; found = true; } j++; } i++; } // if there is a transparent pixel in the bitmap that covers an area // of the image, the fiter will be used. If no such pixel has been found that // means that there either is no transparent pixel in the bitmap or part // of the bitmap that covers part of tha image is not within the borders of // the image (i.e. bitmap is larger than image) if (transX != -1) { filter(x - 1, y - 1, x + bitmapWidth, y + bitmapHeight, filter, this.image.getRGB(transX + x, transY + y)); } } else { filter(x - 1, y - 1, x + bitmapWidth, y + bitmapHeight, filter, -1); } } /** * antialiasing filter for a square part of the image * @param ulx x-value for upper left coordinate * @param uly y-value for upper left coordinate * @param lrx x-value for lower right coordinate * @param lry y-value for lower right coordinate * @param rgb color of background */ public void antialiasing(final int ulx, final int uly, final int lrx, final int lry, final int bgcolor) { filter(ulx, uly, lrx, lry, FilterMode.FILTER_ANTIALIASING, bgcolor); } /** * blur filter for a square part of the image * @param ulx x-value for upper left coordinate * @param uly y-value for upper left coordinate * @param lrx x-value for lower right coordinate * @param lry y-value for lower right coordinate */ public void blur(final int ulx, final int uly, final int lrx, final int lry) { filter(ulx, uly, lrx, lry, FilterMode.FILTER_BLUR, -1); } /** * invert filter for a square part of the image * @param ulx x-value for upper left coordinate * @param uly y-value for upper left coordinate * @param lrx x-value for lower right coordinate * @param lry y-value for lower right coordinate */ public void invert(final int ulx, final int uly, final int lrx, final int lry) { filter(ulx, uly, lrx, lry, FilterMode.FILTER_INVERT, -1); } /** * filter for a square part of the ymageMatrix * @param ulx x-value for upper left coordinate * @param uly y-value for upper left coordinate * @param lrx x-value for lower right coordinate * @param lry y-value for lower right coordinate * @param filter chooses filter */ private void filter(final int ulx, final int uly, final int lrx, final int lry, final FilterMode filter, final int bgcolor) { // taking care that all values are legal final int lox = Math.min(Math.max(Math.min(ulx, lrx), 0), this.width - 1); final int loy = Math.min(Math.max(Math.min(uly, lry), 0), this.height -1); final int rux = Math.min(Math.max(Math.max(ulx, lrx), 0), this.width - 1); final int ruy = Math.min(Math.max(Math.max(uly, lry), 0), this.height -1); int numberOfNeighbours = 0; int rgbR = 0; int rgbG = 0; int rgbB = 0; int rgb = 0; final int width2 = rux - lox + 1; final int height2 = ruy - loy + 1; boolean border = false; final BufferedImage image2 = new BufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB); for (int i = lox; i < rux + 1; i++) { for (int j = loy; j < ruy + 1; j++) { numberOfNeighbours = 0; rgbR = 0; rgbG = 0; rgbB = 0; if (filter == FilterMode.FILTER_ANTIALIASING || filter == FilterMode.FILTER_BLUR) { // taking samples from neighbouring pixel if (i > lox) { rgb = this.image.getRGB(i - 1, j); border = (rgb == bgcolor); rgbR += rgb >> 16 & 0xff; rgbG += rgb >> 8 & 0xff; rgbB += rgb & 0xff; numberOfNeighbours++; } if (j > loy) { rgb = this.image.getRGB(i, j - 1); border = border || (rgb == bgcolor); rgbR += rgb >> 16 & 0xff; rgbG += rgb >> 8 & 0xff; rgbB += rgb & 0xff; numberOfNeighbours++; } if (i < this.width - 1) { rgb = this.image.getRGB(i + 1, j); border = border || (rgb == bgcolor); rgbR += rgb >> 16 & 0xff; rgbG += rgb >> 8 & 0xff; rgbB += rgb & 0xff; numberOfNeighbours++; } if (i < this.height - 1) { rgb = this.image.getRGB(i, j + 1); border = border || (rgb == bgcolor); rgbR += rgb >> 16 & 0xff; rgbG += rgb >> 8 & 0xff; rgbB += rgb & 0xff; numberOfNeighbours++; } } rgb = this.image.getRGB(i, j); // add value of pixel // in case filter is used for antialiasing this will only be done if // the pixel is on the edge to the background color if (filter == FilterMode.FILTER_ANTIALIASING && border || filter == FilterMode.FILTER_BLUR) { rgbR += (rgb >> 16 & 0xff); rgbG += (rgb >> 8 & 0xff); rgbB += (rgb & 0xff); numberOfNeighbours++; border = false; } // set to value of pixel => keep value else if (filter == FilterMode.FILTER_ANTIALIASING) { rgbR = (rgb >> 16 & 0xff); rgbG = (rgb >> 8 & 0xff); rgbB = (rgb & 0xff); numberOfNeighbours = 1; } // set value of pixel to inverted value (using XOR) else if (filter == FilterMode.FILTER_INVERT) { rgb = rgb ^ 0xffffff; rgbR = (rgb >> 16 & 0xff); rgbG = (rgb >> 8 & 0xff); rgbB = (rgb & 0xff); numberOfNeighbours = 1; } // calculating the average rgbR = (rgbR / numberOfNeighbours); rgbG = (rgbG / numberOfNeighbours); rgbB = (rgbB / numberOfNeighbours); rgb = (rgbR << 16) | (rgbG << 8) | rgbB; image2.setRGB(i-lox, j-loy, rgb); } } // insert new version of area into image insertBitmap(image2, lox, loy); } public static void demoPaint(final RasterPlotter m) { m.setColor(GREY); m.line(0, 70, 100, 70, 100); PrintTool.print(m, 0, 65, 0, "Grey", -1, 100); m.line(65, 0, 65, 300, 100); m.setColor(RED); m.line(0, 90, 100, 90, 100); PrintTool.print(m, 0, 85, 0, "Red", -1, 100); m.line(70, 0, 70, 300, 100); m.setColor(GREEN); m.line(0, 110, 100, 110, 100); PrintTool.print(m, 0, 105, 0, "Green", -1, 100); m.line(75, 0, 75, 300, 100); m.setColor(BLUE); m.line(0, 130, 100, 130, 100); PrintTool.print(m, 0, 125, 0, "Blue", -1, 100); m.line(80, 0, 80, 300, 100); } public BufferedImage toIndexed() { Set<Integer> colors = new TreeSet<Integer>(); int[] c = new int[3]; for (int y = this.getHeight() - 1; y >= 0; y--) { for (int x = this.getWidth() - 1; x >= 0; x--) { c = getPixel(x, y, c); colors.add((c[0]<<16)|(c[1]<<8)|c[2]); } } int[] cmap = new int[colors.size()]; int i = 0; for (Integer cc: colors) { cmap[i++] = cc.intValue(); if (i > 255) break; } int bitCount = 1; while ((colors.size() - 1) >> bitCount != 0) bitCount *= 2; IndexColorModel cm = new IndexColorModel(bitCount, colors.size(), cmap, 0, DataBuffer.TYPE_BYTE, null); /* byte [] data = null; int bytesPerRow = this.getWidth()/8 + (this.getWidth()%8!=0?1:0); data = new byte[this.getHeight() * bytesPerRow]; DataBuffer db = new DataBufferByte(data, data.length); WritableRaster wr = Raster.createPackedRaster(db, this.getWidth(), this.getHeight(), 1, null); BufferedImage dest = new BufferedImage(cm, wr, false, null); */ BufferedImage dest = new BufferedImage(this.getWidth(), this.getHeight(), cm.getPixelSize() < 8 ? BufferedImage.TYPE_BYTE_BINARY : BufferedImage.TYPE_BYTE_INDEXED, cm); dest.createGraphics().drawImage(this.getImage(), 0, 0, null); return dest; } public static BufferedImage convertToIndexed(BufferedImage src) { BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_INDEXED); dest.createGraphics().drawImage(src,0,0, null); return dest; } /** * Encode buffered image using specified format to a new ByteBuffer * @param image image to encode * @param targetExt format name. For example "png". * @return a ByteBuffer instance containing encoded data, or empty if an error occured or target format is not supported. */ public static ByteBuffer exportImage(final BufferedImage image, final String targetExt) { // generate an byte array from the given image final ByteBuffer baos = new ByteBuffer(); ImageIO.setUseCache(false); // because we write into ram here try { /* When no ImageIO writer is found image might no be written*/ ImageIO.write(image, targetExt, baos); return baos; } catch (final IOException e) { // should not happen ConcurrentLog.logException(e); return null; } } public ByteBuffer exportPng() { try { final ByteBuffer baos = new ByteBuffer(); byte[] pngbytes = pngEncode(1); if (pngbytes == null) return null; baos.write(pngbytes); baos.flush(); baos.close(); return baos; } catch (final IOException e) { // should not happen ConcurrentLog.logException(e); return null; } } /** * save the image to a file * @param file the storage file * @param type the file type, may be i.e. 'png' or 'gif' * @throws IOException */ public void save(File file, String type) throws IOException { final FileOutputStream fos = new FileOutputStream(file); ImageIO.write(this.image, type, fos); fos.close(); } /** * show the image as JFrame on desktop */ public void show() { JLabel label = new JLabel(new ImageIcon(this.image)); JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(label); f.pack(); f.setVisible(true); } /* * The following code was transformed from a library, coded by J. David Eisenberg, version 1.5, 19 Oct 2003 (C) LGPL * This code was very strongly transformed into the following very short method for an ultra-fast png generation. * These changes had been made 23.10.2012 by [MC] to the original code: * For the integration into YaCy this class was adopted to YaCy graphics by Michael Christen: * - removed alpha encoding * - removed not used code * - inlined static values * - inlined all methods that had been called only once * - moved class objects which appear after all refactoring only within a single method into this method * - removed a giant number of useless (obvious things) comments and empty lines to increase readability (!) * - new order of data computation: first compute the size of compressed deflater output, * then assign an exact-sized byte[] which makes resizing afterwards superfluous * - after all enhancements all class objects were removed; result is just one short static method * - made objects final where possible * - removed the PixelGrabber call and replaced it with a call to this.frame which is just a byte[] * - added more speed woodoo like a buffer around the deflater which makes this much faster */ private static final byte IHDR[] = {73, 72, 68, 82}; private static final byte IDAT[] = {73, 68, 65, 84}; private static final byte IEND[] = {73, 69, 78, 68}; public final byte[] pngEncode(final int compressionLevel) throws IOException { if (this.frame == null) return exportImage(this.getImage(), "png").getBytes(); final int width = image.getWidth(null); final int height = image.getHeight(null); final Deflater scrunch = new Deflater(compressionLevel); ByteBuffer outBytes = new ByteBuffer(1024); final OutputStream compBytes = new BufferedOutputStream(new DeflaterOutputStream(outBytes, scrunch)); int i = 0; for (int row = 0; row < height; row++) { compBytes.write(0); // this replaces the whole PixelGrabber process which makes it probably more than 800x faster. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4835595 compBytes.write(frame, i, 3 * width); i += 3 * width; } compBytes.close(); scrunch.finish(); // finally write the result of the concurrent calculation into an DeflaterOutputStream to compress the png final int nCompressed = outBytes.length(); final byte[] pngBytes = new byte[nCompressed + 57]; // yes thats the exact size, not too less, not too much. No resizing needed. int bytePos = writeBytes(pngBytes, new byte[]{-119, 80, 78, 71, 13, 10, 26, 10}, 0); final int startPos = bytePos = writeInt4(pngBytes, 13, bytePos); bytePos = writeBytes(pngBytes, IHDR, bytePos); bytePos = writeInt4(pngBytes, width, bytePos); bytePos = writeInt4(pngBytes, height, bytePos); bytePos = writeBytes(pngBytes, new byte[]{8, 2, 0, 0, 0}, bytePos); final CRC32 crc = new CRC32(); crc.reset(); crc.update(pngBytes, startPos, bytePos - startPos); bytePos = writeInt4(pngBytes, (int) crc.getValue(), bytePos); crc.reset(); bytePos = writeInt4(pngBytes, nCompressed, bytePos); bytePos = writeBytes(pngBytes, IDAT, bytePos); crc.update(IDAT); outBytes.copyTo(pngBytes, bytePos); outBytes.close(); outBytes = null; crc.update(pngBytes, bytePos, nCompressed); bytePos += nCompressed; bytePos = writeInt4(pngBytes, (int) crc.getValue(), bytePos); bytePos = writeInt4(pngBytes, 0, bytePos); bytePos = writeBytes(pngBytes, IEND, bytePos); crc.reset(); crc.update(IEND); bytePos = writeInt4(pngBytes, (int) crc.getValue(), bytePos); return pngBytes; } private final static int writeInt4(final byte[] target, final int n, final int offset) { return writeBytes(target, new byte[]{(byte) ((n >> 24) & 0xff), (byte) ((n >> 16) & 0xff), (byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}, offset); } private final static int writeBytes(final byte[] target, final byte[] data, final int offset) { System.arraycopy(data, 0, target, offset, data.length); return offset + data.length; } public static void main(final String[] args) { // go into headless awt mode System.setProperty("java.awt.headless", "true"); final RasterPlotter m = new RasterPlotter(200, 300, DrawMode.MODE_SUB, "FFFFFF"); demoPaint(m); final File file = new File("/Users/admin/Desktop/testimage.png"); try { final FileOutputStream fos = new FileOutputStream(file); ImageIO.write(m.getImage(), "png", fos); fos.close(); } catch (final IOException e) {} ConcurrentLog.shutdown(); // open file automatically, works only on Mac OS X /* Process p = null; try {p = Runtime.getRuntime().exec(new String[] {"/usr/bin/osascript", "-e", "open \"" + args[0] + "\""});} catch (final java.io.IOException e) {Log.logException(e);} try {p.waitFor();} catch (final InterruptedException e) {Log.logException(e);} */ } }