/*
* Copyright (c) 2001-2005 Frederic Banino. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Frederic Banino nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.frban.graphics.hatch;
import java.lang.ref.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
/**
* un contexte simplifié de hachures, qui utilise un buffer offscreen static,
* afin d'optimiser au mieux la mémoire. Noter aussi que les transformations
* du contexte graphique ne sont pas prises en compte, ce qui rend les hachures
* invariantes à des opérations de rotation ou d'échelle. Cet effet n'est pas
* focément souhaitable, mais c'est celui que je voulais donner.
* @version 1.0
* @author FB
* @see HatchedPaint
*/
public class HatchedPaintContext implements PaintContext {
/** modèle de couleur utilisé pour rendre les hachures */
private static ColorModel cachedModel;
/** référence faible associée au buffer. Avec ce système, on est sûr que
* de garbage collector effacera le cache dès qu'on en a plus besoin.
*/
private static WeakReference cached;
/**
* fabrication d'un buffer image pour que les hachures soient d'abord
* rendues en offscreen. Si le cache existe déjà et que la taille d'image
* n'a pas bougé, on réutilise le même buffer.
* @param cm modèle couleur du buffer
* @param w largeur du buffer
* @param h hauteur du buffer
* @return buffer image prêt à l'emploi
*/
private static synchronized Raster getCachedRaster(ColorModel cm, int w, int h) {
if (cm == cachedModel) {
if (cached != null) {
Raster ras = (Raster) cached.get();
if (ras != null && ras.getWidth() >= w && ras.getHeight() >= h) {
cached = null;
return ras;
}
}
}
return cm.createCompatibleWritableRaster(w, h);
}
/**
* Intègre un buffer en cache, pour rendre les hachures offscreen. Le
* buffer est stocké dans une WeakReference, de sorte qu'il soit libéré
* prioritairement par le garbage collector quand on n'en aura plus besoin.
* Ce procédé assure une gestion mémoire propre.
* @param cm modèle couleur du buffer
* @param ras buffer à mettre en cache.
*/
private static synchronized void putCachedRaster(ColorModel cm, Raster ras) {
if (cached != null) {
Raster cras = (Raster) cached.get();
if (cras != null) {
int cw = cras.getWidth();
int ch = cras.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(ras);
}
//--------------------------------------------------------------------------
/**
* modèle par défaut ColorModel.getRBGdefault, capable de gérer la
* transparence (ARGB);
*/
private static ColorModel argbmodel = ColorModel.getRGBdefault();
/** couleur des hachures */
private int[] colors;
/** espacement entre les hachures */
private float space;
/** angle des hachures */
private float angle;
/** épaisseur de trait */
private float lineWidth;
/** paramètre de formation de couleurs intermédiaires d'antialiasing */
private int aliasMax = 25;
/** transformation de positionnement du gradient */
private AffineTransform gat;
/** raster conservé d'un appel à un autre */
private Raster saved;
/**
* fabrique la table des couleurs d'antialiasing. Cette table comprend
* aliaxMax éléments. La couleur c est situé au milieu, puis des nuances
* de plus en plus transparentes sont crées pour aller vers les extrémités.
* @param c couleur des hachures.
*/
protected void createAliasedColorTable(Color c) {
int cc = c.getRGB();
float a = (0xFF & (cc>>24))/255f; //alpha
cc=0xFFFFFF & cc; //ôte la transparence éventuelle
colors = new int[aliasMax];
//la table est symétrique avec la couleur opaque au centre
//et des nuances de + en + transparentes à g et à d.
for(int i=0; i<aliasMax/2; i++) {
int alpha = (int)(i/(aliasMax/2f-1f)*a*255.0f);
alpha <<= 24;
colors[i] = alpha | cc;
colors[colors.length-i-1] = alpha | cc;
}
//couleur opaque à ajouter au centre du tableau quand celui-ci
//est impair.
if (aliasMax%2!=0) colors[aliasMax/2]= 0xFF000000 | cc;
}
/** nouveau contexte */
public HatchedPaintContext(Color c, float space, float angle, float lineWidth) {
this.space=space;
this.angle=angle;
this.lineWidth = lineWidth;
createAliasedColorTable(c);
gat = new AffineTransform();
//décalage de pi/2 pour que l'angle 0 soit horizontal
gat.rotate(Math.PI/2+angle);
}
/** efface les références */
public void dispose() {
if (saved != null) {
putCachedRaster(argbmodel, saved);
saved = null;
}
}
/**
* renvoie toujours un modèle ARGB
* @see #argbmodel
*/
public ColorModel getColorModel() { return argbmodel; }
/**
* dessine les hachures dans le buffer offscreen.
* @return buffer où ont été dessinées les hachures.
*/
public Raster getRaster(int x, int y, int w, int h) {
float xref=0;
float distance = space;
//création du buffer s'il n'existe pas encore
Raster rast = saved;
if (rast == null || rast.getWidth() < w || rast.getHeight() < h) {
rast = getCachedRaster(argbmodel, w, h);
saved = rast;
}
DataBufferInt buffer = (DataBufferInt) rast.getDataBuffer();
int off = buffer.getOffset();
int adjust = rast.getWidth()-w;
int[] pixels = buffer.getData();
Point2D.Double p1=new Point2D.Double();
Point2D.Double p2=new Point2D.Double();
double r=0;
double line;
for(int yy=0; yy<h; yy++) {
for(int xx=0; xx<w; xx++) {
p1.x = x+xx;
p1.y = y+yy;
gat.transform(p1,p2);
r = (p2.x-xref)/distance;
r = r-Math.floor(r);
line = Math.abs(r*distance);
//dessine si on se trouve proche du trait
pixels[off++] = line<lineWidth ? colors[(int)(line/lineWidth*aliasMax)] : 0;
}
off += adjust;
}
return rast;
}
/** valeur de l'angle */
public float getAngle() {
return angle;
}
}