/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ui;
import sun.awt.image.IntegerComponentRaster;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.lang.ref.WeakReference;
/**
* This class provides gradient paint with dithering (run main() to see the difference)<p/>
* Note, it doesn't support "cyclic" mode.
* Disclaimer: Unfortunately it works much slower than standard GradientPaint
*/
public class JBGradientPaint extends GradientPaint {
public JBGradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2) {
super(x1, y1, color1, x2, y2, color2);
}
public JBGradientPaint(Point2D pt1, Color color1, Point2D pt2, Color color2) {
super(pt1, color1, pt2, color2);
}
@Override
public PaintContext createContext(ColorModel cm,
Rectangle deviceBounds,
Rectangle2D userBounds,
AffineTransform xform,
RenderingHints hints) {
return new JBGradientPaintContext(cm, getPoint1(), getPoint2(), xform, getColor1(), getColor2());
}
public static void main(String[] args) {
final Color c1 = new Color(155, 155, 155);
final Color c2 = new Color(50, 50, 50);
final int size = 500;
JFrame f = new JFrame("JBGradientPaint");
JPanel contentPane = new JPanel(new GridLayout(1, 2, 1, 1));
f.setContentPane(contentPane);
JPanel leftPanel = new JPanel(new BorderLayout()){
@Override
public void paintComponent(Graphics g) {
((Graphics2D)g).setPaint(new GradientPaint(0, 0, c1, size/2, size, c2));
g.fillRect(0, 0, size, size);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
JPanel rightPanel = new JPanel(new BorderLayout()){
@Override
public void paintComponent(Graphics g) {
((Graphics2D)g).setPaint(new JBGradientPaint(0, 0, c1, size/2, size, c2));
g.fillRect(0, 0, size, size);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
leftPanel.add(new JLabel("Standard gradient"), BorderLayout.NORTH);
rightPanel.add(new JLabel("Dithered gradient"), BorderLayout.NORTH);
leftPanel.setOpaque(true);
rightPanel.setOpaque(true);
contentPane.add(leftPanel);
contentPane.add(rightPanel);
f.pack();
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setVisible(true);
}
/**
* Most of the code copied from java.awt.GradientPaintContext
*/
private static class JBGradientPaintContext implements PaintContext {
private static final ColorModel RGBMODEL = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
private static final ColorModel BGRMODEL = new DirectColorModel(24, 0x000000ff, 0x0000ff00, 0x00ff0000);
private static final ThreadLocal<int[][]> RGB_ARRAYS = new ThreadLocal<int[][]>() {
@Override
protected int[][] initialValue() {
return new int[3][256];
}
};
private static final ThreadLocal<int[][][][]> DITHER_ARRAYS = new ThreadLocal<int[][][][]>() {
@Override
protected int[][][][] initialValue() {
return new int[3][256][][];
}
};
//Dithering
private static final double GR = (Math.sqrt(5) - 1) / 2;
private static final int[] IDENTITY_FUNC = new int[256];
private static final int[] SHIFT_FUNC = new int[256];
private static final int[][][] DITHER_MATRIX = new int[256][256][];
static {
for (int i = 0; i < 256; i++) {
IDENTITY_FUNC[i] = i;
SHIFT_FUNC[i] = i + 1;
}
SHIFT_FUNC[255] = 255;
int iter = 0;
for (int i = 0; i < DITHER_MATRIX.length; i++) {
int[][] row = DITHER_MATRIX[i];
for (int j = 0; j < row.length; j++) {
row[j] = IDENTITY_FUNC;
}
for (int j = 0; j < i; j++) {
int pos = (int)(1604419 * GR * iter) % (256 - j);
row[getIndex(row, pos)] = SHIFT_FUNC;
iter++;
}
}
}
private static ColorModel cachedModel;
private static WeakReference<Raster> cached;
private final double myX1;
private final double myY1;
private final double myDx;
private final double myDy;
private final int myRgb1;
private final int myRgb2;
private Raster saved;
private final ColorModel model;
JBGradientPaintContext(ColorModel cm,
Point2D p1, Point2D p2, AffineTransform xform,
Color c1, Color c2) {
Point2D xvec = new Point2D.Double(1, 0);
Point2D yvec = new Point2D.Double(0, 1);
try {
AffineTransform inverse = xform.createInverse();
inverse.deltaTransform(xvec, xvec);
inverse.deltaTransform(yvec, yvec);
}
catch (NoninvertibleTransformException e) {
xvec.setLocation(0, 0);
yvec.setLocation(0, 0);
}
double udx = p2.getX() - p1.getX();
double udy = p2.getY() - p1.getY();
double ulenSq = udx * udx + udy * udy;
if (ulenSq <= Double.MIN_VALUE) {
myDx = 0;
myDy = 0;
}
else {
double dxx = (xvec.getX() * udx + xvec.getY() * udy) / ulenSq;
double dyy = (yvec.getX() * udx + yvec.getY() * udy) / ulenSq;
if (dxx < 0) {
p1 = p2;
Color c = c1;
c1 = c2;
c2 = c;
myDx = -dxx;
myDy = -dyy;
}
else {
myDx = dxx;
myDy = dyy;
}
}
Point2D dp1 = xform.transform(p1, null);
this.myX1 = dp1.getX();
this.myY1 = dp1.getY();
myRgb1 = c1.getRGB();
myRgb2 = c2.getRGB();
int a1 = (myRgb1 >> 24) & 0xff;
int r1 = (myRgb1 >> 16) & 0xff;
int g1 = (myRgb1 >> 8) & 0xff;
int b1 = (myRgb1) & 0xff;
int da = ((myRgb2 >> 24) & 0xff) - a1;
int dr = ((myRgb2 >> 16) & 0xff) - r1;
int dg = ((myRgb2 >> 8) & 0xff) - g1;
int db = ((myRgb2) & 0xff) - b1;
ColorModel m;
if (a1 == 0xff && da == 0) {
m = RGBMODEL;
if (cm instanceof DirectColorModel) {
DirectColorModel dcm = (DirectColorModel)cm;
int tmp = dcm.getAlphaMask();
if ((tmp == 0 || tmp == 0xff) &&
dcm.getRedMask() == 0xff &&
dcm.getGreenMask() == 0xff00 &&
dcm.getBlueMask() == 0xff0000) {
m = BGRMODEL;
tmp = r1;
r1 = b1;
b1 = tmp;
tmp = dr;
dr = db;
db = tmp;
}
}
}
else {
m = ColorModel.getRGBdefault();
}
model = m;
for (int i = 0; i < 256; i++) {
double rel = i / 256.0f;
double rValue = r1 + dr * rel;
double gValue = g1 + dg * rel;
double bValue = b1 + db * rel;
DITHER_ARRAYS.get()[0][i] = DITHER_MATRIX[(int)(rValue * 256) % 256];
DITHER_ARRAYS.get()[1][i] = DITHER_MATRIX[(int)(gValue * 256) % 256];
DITHER_ARRAYS.get()[2][i] = DITHER_MATRIX[(int)(bValue * 256) % 256];
RGB_ARRAYS.get()[0][i] = (int)rValue;
RGB_ARRAYS.get()[1][i] = (int)gValue;
RGB_ARRAYS.get()[2][i] = (int)bValue;
}
}
static synchronized Raster getCachedRaster(ColorModel cm, int w, int h) {
if (cm == cachedModel) {
if (cached != null) {
Raster ras = cached.get();
if (ras != null &&
ras.getWidth() >= w &&
ras.getHeight() >= h) {
cached = null;
return ras;
}
}
}
return cm.createCompatibleWritableRaster(w, h);
}
static synchronized void putCachedRaster(ColorModel cm, Raster ras) {
if (cached != null) {
Raster raster = cached.get();
if (raster != null) {
int cw = raster.getWidth();
int ch = raster.getHeight();
int iw = ras.getWidth();
int ih = ras.getHeight();
if (cw >= iw && ch >= ih) {
return;
}
if (cw * ch >= iw * ih) {
return;
}
}
}
cachedModel = cm;
cached = new WeakReference<Raster>(ras);
}
private static int getIndex(int[][] arr, int pos) {
for (int i = 0; i < arr.length; i++) {
int[] f = arr[i];
if (f == IDENTITY_FUNC) {
pos--;
}
if (pos < 0) {
return i;
}
}
throw new IllegalArgumentException();
}
public void dispose() {
if (saved != null) {
putCachedRaster(model, saved);
saved = null;
}
}
public ColorModel getColorModel() {
return model;
}
public Raster getRaster(int x, int y, int w, int h) {
double rowrel = (x - myX1) * myDx + (y - myY1) * myDy;
Raster rast = saved;
if (rast == null || rast.getWidth() < w || rast.getHeight() < h) {
rast = getCachedRaster(model, w, h);
saved = rast;
}
IntegerComponentRaster irast = (IntegerComponentRaster)rast;
int off = irast.getDataOffset(0);
int adjust = irast.getScanlineStride() - w;
int[] pixels = irast.getDataStorage();
clipFillRaster(pixels, off, adjust, w, h, rowrel, myDx, myDy);
return rast;
}
void clipFillRaster(int[] pixels, int off, int adjust, int w, int h, double rowrel, double dx, double dy) {
while (--h >= 0) {
double colrel = rowrel;
int j = w;
if (colrel <= 0.0) {
int rgb = myRgb1;
do {
pixels[off++] = rgb;
colrel += dx;
}
while (--j > 0 && colrel <= 0.0);
}
while (colrel < 1.0 && --j >= 0) {
int offrel = off & 0xFF;
int idx = (int)(colrel * 256);
int rresult = DITHER_ARRAYS.get()[0][idx][offrel][RGB_ARRAYS.get()[0][idx]];
int gresult = DITHER_ARRAYS.get()[1][idx][offrel][RGB_ARRAYS.get()[1][idx]];
int bresult = DITHER_ARRAYS.get()[2][idx][offrel][RGB_ARRAYS.get()[2][idx]];
pixels[off++] = (rresult << 16) | (gresult << 8) | bresult;
colrel += dx;
}
if (j > 0) {
int rgb = myRgb2;
do {
pixels[off++] = rgb;
}
while (--j > 0);
}
off += adjust;
rowrel += dy;
}
}
}
}