/* * 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; } }