/* * Copyright 2009 Rodrigo Reyes reyes.rr at gmail dot com * * 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 net.kornr.swit.wicket.border.graphics; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import net.kornr.swit.util.LRUMap; import org.apache.wicket.AttributeModifier; import org.apache.wicket.RequestCycle; import org.apache.wicket.Resource; import org.apache.wicket.ResourceReference; import org.apache.wicket.markup.html.WebResource; import org.apache.wicket.model.Model; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.util.resource.FileResourceStream; import org.apache.wicket.util.resource.IResourceStream; import org.apache.wicket.util.value.ValueMap; /** * The BorderMaker is the root class for creating pictures suitable for HTML layout. Classes inheriting BorderMaker * create pictures that can be divided in 9 parts, each part can be used as a stand alone picture to create borders * for an html block. * <p> * Borders are created using a factory class that returns an numeric ID that uniquely identifies the Border. Some * Border are combined with other borders using this ID. * </p> * <p> * Examples: * * <pre> * Long roundId = RoundedBorderMaker.register(6, 3, Color.magenta, Color.red); * TableImageBorder border = new TableImageBorder("test", roundId, Color.yellow); * </pre> * * <pre> * Long glossy = GlossyRoundedBorderMaker.register(6, 12, Color.blue, Color.blue); * Long shadow = GenericShadowBorder.register(glossy, 3, 1, 16, null); * TableImageBorder border = new TableImageBorder("test", shadow, Color.yellow); * </pre> * * A blue glossy rounded border, with a shadow, and a 32pixel left margin, and a 64 pixels right margin. * <pre> * Long glossy = GlossyRoundedBorderMaker.register(6, 12, Color.blue, Color.blue); * Long shadow = GenericShadowBorder.register(glossy, 3, 1, 16, null); * Long margin = MarginBorder.register(shadow, 0, 0, 32, 64, Color.pink); * TableImageBorder border = new TableImageBorder("test", margin, Color.yellow); * </pre> * * It is also possible to reference directly an image: * <pre> * Long margin = MarginBorder.register(shadow, 0, 0, 32, 64, Color.pink); * Long mypic = SizedBorder.register(margin, 640, 480); * String myImageUrl = BorderMaker.getUrl(mypic, "full", false); * </pre> * </p> * <p> * Registered borders stay in memory forever, which is usually what most applications want. If for some reason the border * is only used temporarily, the static registerTemporary() method uses a LRU cache, with a limited number of slots, * least recent borders being discarded when no more slots are available. * </p> * @author Rodrigo * */ abstract public class BorderMaker extends WebResource { protected Long m_id; private HashMap<String,File> m_cache = new HashMap<String,File>(); private int m_width; private int m_height; private static HashMap<Long, BorderMaker> s_regCache = new HashMap<Long, BorderMaker>(); private static Map<Long, BorderMaker> s_regTemporaryCache = new LRUMap<Long, BorderMaker>(1000); private static long s_counterName = System.currentTimeMillis(); public abstract BufferedImage createIndexedImage(Rectangle part); public abstract BufferedImage createImage(Rectangle part); protected BorderMaker() { } protected BorderMaker(int width, int height) { m_width = width; m_height = height; } /** * Defines the size of the LRU cache for temporary borders. Setting this value discards the temporary cache. * */ synchronized static public void setTemporaryCacheSize(int temporaryCachesize) { s_regTemporaryCache = new LRUMap<Long, BorderMaker>(temporaryCachesize); } /** * Registers a border in a temporary cache. Not for normal use cases. * @return a id that uniquely identifies the border in the cache. */ synchronized static public Long registerTemporary(BorderMaker bm) { Long l = s_counterName++; bm.m_id = l; s_regTemporaryCache.put(l, bm); return l; } /** * Registers a border in the global cache. * @return a id that uniquely identifies the border in the cache. */ synchronized static public Long register(BorderMaker bm) { Long l = s_counterName++; bm.m_id = l; s_regCache.put(l, bm); return l; } static public BorderMaker get(Long id) { return s_regCache.get(id); } /** * */ public static void install(String mapping) { WebApplication.get().getSharedResources().add(BorderMaker.class, "image", null, null, new BorderMaker() { @Override public BufferedImage createImage(Rectangle part) { return null; } @Override public BufferedImage createIndexedImage(Rectangle part) { return null; } @Override public ImageMap getImageMap() { return null; } }); ResourceReference ref = getReference(); WebApplication.get().mountSharedResource(mapping, ref.getSharedResourceKey()); } public static ResourceReference getReference() { return new ResourceReference(BorderMaker.class, "image") { @Override protected Resource newResource() { return new BorderMaker() { @Override public BufferedImage createImage(Rectangle part) { return null; } @Override public BufferedImage createIndexedImage(Rectangle part) { return null; } @Override public ImageMap getImageMap() { return null; } }; } }; } public static org.apache.wicket.markup.html.image.Image getImage(String id, Long imageId, String type, boolean indexed) { ValueMap args = new ValueMap(); args.put("border", type); args.put("id", imageId.toString()); args.put("type", indexed?"indexed":"rgb"); ResourceReference ref = getReference(); org.apache.wicket.markup.html.image.Image img = new org.apache.wicket.markup.html.image.Image(id, ref, args); Dimension dim = BorderMaker.get(imageId).getMapSize(type); img.add(new AttributeModifier("width", true, new Model<String>(""+dim.width))); img.add(new AttributeModifier("height", true, new Model<String>(""+dim.height))); return img; } public static String getUrl(Long imageId, String type, boolean indexed) { ValueMap args = new ValueMap(); args.put("border", type); args.put("id", imageId.toString()); args.put("type", indexed?"indexed":"rgb"); ResourceReference ref = getReference(); return RequestCycle.get().urlFor(ref, args).toString(); } static protected BorderMaker retrieveFromCache(Long id) { return s_regCache.get(id); } @Override public IResourceStream getResourceStream() { ValueMap map = this.getParameters(); String border = map.getString("border", "full"); Long id = map.getAsLong("id"); String indx = map.getString("type", "rgb"); BorderMaker processor = this; if (id!=null) { processor = s_regCache.get(id); if (processor==null) processor = s_regTemporaryCache.get(id); } return processor.process(border, indx.equals("indexed")); } protected String getUniqueName(String border, boolean indexedImage) { return (indexedImage?"indexed":"rgb")+"-"+border; } synchronized public IResourceStream process(String border, boolean indexedImage) { String filename = getUniqueName(border, indexedImage); boolean debug = false; if (border.equals("debug")) { border = "full"; debug = true; } File result = m_cache.get(filename); if (result != null) { return new FileResourceStream(result); } ImageMap map = getImageMap(); if (indexedImage) { BufferedImage img = createIndexedImage(map.getZone(border)); File f = createFile(img, "gif", filename); m_cache.put(filename, f); return new FileResourceStream(f); } else { BufferedImage img = createImage(map.getZone(border)); if (debug) debugMap(img, map); File f = createFile(img, "png", filename); m_cache.put(filename, f); return new FileResourceStream(f); } } private void debugMap(BufferedImage img, ImageMap map) { Graphics2D g = img.createGraphics(); g.setColor(new Color(30,255,255,200)); String[] vars = { "tl", "t", "tr", "r", "br", "b", "bl", "l" }; for (String s : vars) { Rectangle r = map.getZone(s); g.drawRect(r.x, r.y, r.width, r.height); } } /** * Default image map is 0,1/3,2/3,3/3 mapping * @return */ abstract public ImageMap getImageMap(); // public BufferedImage convertRGBAToIndexed(BufferedImage src) { // BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, createModel(new Color(231,20,189))); // Graphics g = dest.getGraphics(); // g.setColor(new Color(231,20,189)); // g.fillRect(0, 0, dest.getWidth(), dest.getHeight()); //fill with a hideous color and make it transparent // // dest = makeTransparent(dest,0,0); // dest.createGraphics().drawImage(src,0,0, null); // return dest; // } // public BufferedImage makeTransparent(BufferedImage image, int x, int y) { // ColorModel cm = image.getColorModel(); // if (!(cm instanceof IndexColorModel)) // return image; //sorry... // IndexColorModel icm = (IndexColorModel) cm; // WritableRaster raster = image.getRaster(); // int pixel = raster.getSample(x, y, 0); //pixel is offset in ICM's palette // int size = icm.getMapSize(); // byte[] reds = new byte[size]; // byte[] greens = new byte[size]; // byte[] blues = new byte[size]; // icm.getReds(reds); // icm.getGreens(greens); // icm.getBlues(blues); // IndexColorModel icm2 = new IndexColorModel(8, size, reds, greens, blues, pixel); // icm2 = createModel(new Color(231,20,189)); // return new BufferedImage(icm2, raster, image.isAlphaPremultiplied(), null); // } public File createFile(BufferedImage img, String format, String name) { final String baseTempPath = System.getProperty("java.io.tmpdir"); File dir = new File(baseTempPath, BorderMaker.class.getCanonicalName()); File subdir = new File(dir, m_id.toString()); subdir.mkdirs(); File result = new File(subdir, name); // result.delete(); try { ImageIO.write(img, format, result); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } public Dimension getMapSize(String part) { Rectangle p = getImageMap().getZone(part); return new Dimension(p.width, p.height); } public int getWidth() { return m_width; } public void setWidth(int width) { m_width = width; } public int getHeight() { return m_height; } public void setHeight(int height) { m_height = height; } }