// Copyright 2006, FreeHEP. package org.freehep.graphicsio.emf.gdiplus; import java.awt.BasicStroke; import java.awt.Color; import java.awt.GradientPaint; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.Properties; import org.freehep.graphicsio.ImageGraphics2D; import org.freehep.graphicsio.emf.EMFInputStream; import org.freehep.graphicsio.emf.EMFOutputStream; /** * The Object metafile record contains information on an object which must be * stored for use by a future metafile record. Examples include pens, brushes, * fonts, images, StringFormat objects, and so on. Naturally, the data format of * each type of object differs, and so the Flags value is used to determine * which type of object is being stored. * <p> * Object metafile records contain a numeric index (0 to 255). The first Object * record to appear in the file will have an index of 0, the next record 1, and * so on. Applications which process metafiles should store these objects in an * index-based collection so that a later record can access them. For example, a * future DrawRects record might reference the Pen with index 2, and the * application should ensure that that object is still accessible. Since the * index is only one byte, only 256 objects can be stored at any time. Thus, a * simple stack will not suffice and applications will have to be prepared for * new Object records overwriting old ones with the same index. * * FIXME: no support for 16 bit * * @author Mark Donszelmann * @version $Id: GDIPlusObject.java,v 1.1 2009-08-17 21:44:44 murkle Exp $ */ public class GDIPlusObject extends EMFPlusTag { protected final static int INVALID = 0x000; protected final static int BRUSH = 0x100; protected final static int PEN = 0x200; protected final static int PATH = 0x300; protected final static int REGION = 0x400; protected final static int IMAGE = 0x500; protected final static int FONT = 0x600; protected final static int STRING_FORMAT = 0x700; protected final static int IMAGE_ATTRIBUTES = 0x800; protected final static int CUSTOM_LINE_CAP = 0x900; protected final static int BRUSH_TYPE_SOLID_COLOR = 0; protected final static int BRUSH_TYPE_HATCH_FILL = 1; protected final static int BRUSH_TYPE_TEXTURE_GRADIENT = 2; protected final static int BRUSH_TYPE_PATH_GRADIENT = 3; protected final static int BRUSH_TYPE_LINEAR_GRADIENT = 4; protected final static int WRAP_MODE_TYLE = 0; protected final static int WRAP_MODE_TYLE_FLIP_X = 1; protected final static int WRAP_MODE_TYLE_FLIP_Y = 2; protected final static int WRAP_MODE_TYLE_FLIP_XY = 3; protected final static int WRAP_MODE_CLAMP = 4; protected final static int IMAGE_TYPE_UNKNOWN = 0; protected final static int IMAGE_TYPE_BITMAP = 1; protected final static int IMAGE_TYPE_METAFILE = 2; protected final static int FILL_MODE_ALTERNATE = 0x0000; protected final static int FILL_MODE_WINDING = 0x2000; private Paint brush; private BasicStroke stroke; private PathPoint[] path; private int pathFillMode; private RenderedImage image; public GDIPlusObject() { super(8, 1); } public GDIPlusObject(int index, Paint brush) { this(); this.brush = brush; flags = index | BRUSH; } public GDIPlusObject(int index, Stroke stroke, Paint brush) { this(); if (!(stroke instanceof BasicStroke)) { throw new IllegalArgumentException(getClass() + ": can only handle Stroke of class BasicStroke"); } this.stroke = (BasicStroke) stroke; this.brush = brush; flags = index | PEN; } public GDIPlusObject(int index, Shape shape, boolean windingFill) { this(); try { EMFPlusPathConstructor p = new EMFPlusPathConstructor(); p.reset(); p.addPath(shape); path = p.getPath(); flags = index | PATH; pathFillMode = windingFill ? FILL_MODE_WINDING : FILL_MODE_ALTERNATE; } catch (IOException e) { // ignored } } public GDIPlusObject(int index, RenderedImage image) { this(); this.image = image; flags = index | IMAGE; } @Override public EMFPlusTag read(int tagID, int flags, EMFInputStream emf, int len) throws IOException { GDIPlusObject tag = new GDIPlusObject(); tag.flags = flags; int type = flags & 0x0000FF00; // FIXME some missing switch (type) { case BRUSH: tag.brush = readBrush(emf); break; case PEN: emf.readUINT(); // magic word emf.readUINT(); // unknown emf.readUINT(); // additional flags, NOTE no join, endcap, // miterlimit etc. emf.readUINT(); // unknown float lineWidth = emf.readFLOAT(); tag.stroke = new BasicStroke(lineWidth); tag.brush = readBrush(emf); break; case PATH: emf.readUINT(); // magic word tag.path = new PathPoint[emf.readUINT()]; int moreFlags = emf.readUINT(); pathFillMode = moreFlags & 0x2000; for (int i = 0; i < tag.path.length; i++) { tag.path[i] = new PathPoint(); tag.path[i].setX(emf.readFLOAT()); tag.path[i].setY(emf.readFLOAT()); } for (int i = 0; i < tag.path.length; i++) { tag.path[i].setType(emf.readUnsignedByte()); } if (tag.path.length % 4 > 0) { for (int i = 4 - (tag.path.length % 4); i > 0; i--) { emf.readBYTE(); } } break; case INVALID: default: System.err.println( "GDIObject: Invalid TYPE: " + Integer.toHexString(type)); break; } return tag; } @Override public void write(int tagID, int flags, EMFOutputStream emf) throws IOException { int type = flags & 0x0000FF00; switch (type) { case BRUSH: writeBrush(emf, brush); break; case PEN: emf.writeUINT(0xDBC01001); emf.writeUINT(0x0000); // unknown emf.writeUINT(0x0000); // additional flags, NOTE no join, endcap, // miterlimit etc. emf.writeUINT(0x0000); // unknown emf.writeFLOAT(stroke.getLineWidth()); writeBrush(emf, brush); break; case PATH: emf.writeUINT(0xDBC01001); emf.writeUINT(path.length); emf.writeUINT(pathFillMode); for (int i = 0; i < path.length; i++) { emf.writeFLOAT(path[i].getX()); emf.writeFLOAT(path[i].getY()); } for (int i = 0; i < path.length; i++) { emf.writeUnsignedByte(path[i].getType()); } if (path.length % 4 > 0) { for (int i = 4 - (path.length % 4); i > 0; i--) { emf.writeBYTE(0); } } break; case IMAGE: writeImage(emf, image); break; case INVALID: default: break; } } @Override public String toString() { StringBuffer sb = new StringBuffer(super.toString()); sb.append("\n "); int type = flags & 0x0000FF00; switch (type) { case BRUSH: sb.append("brush: " + brush); break; case PEN: sb.append("stroke: " + stroke); sb.append("\n brush: " + brush); break; case PATH: sb.append("fillMode: " + pathFillMode); sb.append("\n n: " + path.length); for (int i = 0; i < path.length; i++) { sb.append("\n 0x" + Integer.toHexString(path[i].getType()) + " (" + path[i].getX() + ", " + path[i].getY() + ")"); } break; default: sb.append("UNKNOWN"); break; } return sb.toString(); } private static Paint readBrush(EMFInputStream emf) throws IOException { emf.readUINT(); // magic word int brushType = emf.readUINT(); switch (brushType) { case BRUSH_TYPE_SOLID_COLOR: return emf.readCOLOR(); case BRUSH_TYPE_LINEAR_GRADIENT: emf.readUINT(); // special mode ignored // FIXME, rest to be done return null; /* * emf.writeUINT(paint.isCyclic() ? WRAP_MODE_TYLE_FLIP_XY : * WRAP_MODE_CLAMP); // NOTE: check float x1 = * (float)paint.getPoint1().getX(); float y1 = * (float)paint.getPoint1().getY(); float x2 = * (float)paint.getPoint2().getX(); float y2 = * (float)paint.getPoint2().getY(); emf.writeFLOAT(Math.min(x1, x2)); * emf.writeFLOAT(Math.min(y1, y2)); emf.writeFLOAT(Math.abs(x1-x2)); * emf.writeFLOAT(Math.abs(y1-y2)); emf.writeCOLOR(paint.getColor1()); * emf.writeCOLOR(paint.getColor2()); emf.writeCOLOR(paint.getColor1()); * emf.writeCOLOR(paint.getColor2()); } else if (brush instanceof * TexturePaint) { // emf.writeUINT(BRUSH_TYPE_TEXTURE_GRADIENT); * //FIXME later when image is done * emf.writeUINT(BRUSH_TYPE_SOLID_COLOR); emf.writeCOLOR(Color.BLACK); } * else { System.err.println("No Brush for paint of class: " * +brush.getClass()+" defaulting to black"); * emf.writeUINT(BRUSH_TYPE_SOLID_COLOR); emf.writeCOLOR(Color.BLACK); } */ } return Color.BLACK; } private static void writeBrush(EMFOutputStream emf, Paint brush) throws IOException { emf.writeUINT(0xDBC01001); if (brush instanceof Color) { emf.writeUINT(BRUSH_TYPE_SOLID_COLOR); emf.writeCOLOR((Color) brush); } else if (brush instanceof GradientPaint) { GradientPaint paint = (GradientPaint) brush; emf.writeUINT(BRUSH_TYPE_LINEAR_GRADIENT); emf.writeUINT(0x02); // write Matrix emf.writeUINT(paint.isCyclic() ? WRAP_MODE_TYLE_FLIP_XY : WRAP_MODE_TYLE_FLIP_Y); // NOTE: check float x1 = (float) paint.getPoint1().getX(); float y1 = (float) paint.getPoint1().getY(); float x2 = (float) paint.getPoint2().getX(); float y2 = (float) paint.getPoint2().getY(); emf.writeFLOAT(Math.min(x1, x2)); emf.writeFLOAT(Math.min(y1, y2)); emf.writeFLOAT(Math.abs(x1 - x2)); emf.writeFLOAT(Math.abs(y1 - y2)); emf.writeCOLOR(paint.getColor1()); emf.writeCOLOR(paint.getColor2()); emf.writeCOLOR(paint.getColor1()); emf.writeCOLOR(paint.getColor2()); float dx = x2 - x1; float dy = y2 - y1; float scale = (float) paint.getPoint1().distance(paint.getPoint2()) / dx; System.err.println(paint.getPoint1() + " " + paint.getPoint2()); System.err.println(x1 + " " + x2 + " " + y1 + " " + y2 + ":" + dx + " " + dy + " " + scale); float angle = (float) Math.atan2(dy, dx); AffineTransform transform = new AffineTransform(scale, 0, 0, scale, dx / 2 + x1, dy / 2 + y1); transform = new AffineTransform(); transform.scale(scale, scale); transform.rotate(angle); writeTransform(emf, transform); } else if (brush instanceof TexturePaint) { TexturePaint paint = (TexturePaint) brush; emf.writeUINT(BRUSH_TYPE_TEXTURE_GRADIENT); emf.writeUINT(0); // no special mode emf.writeUINT(WRAP_MODE_TYLE); writeImage(emf, paint.getImage()); } else { System.err.println("No Brush for paint of class: " + brush.getClass() + " defaulting to black"); emf.writeUINT(BRUSH_TYPE_SOLID_COLOR); emf.writeCOLOR(Color.BLACK); } } public static void writeTransform(EMFOutputStream emf, AffineTransform transform) throws IOException { emf.writeFLOAT((float) transform.getScaleX()); emf.writeFLOAT((float) transform.getShearY()); emf.writeFLOAT((float) transform.getShearX()); emf.writeFLOAT((float) transform.getScaleY()); emf.writeFLOAT((float) transform.getTranslateX()); emf.writeFLOAT((float) transform.getTranslateY()); } private static void writeImage(EMFOutputStream emf, RenderedImage image) throws IOException { emf.writeUINT(0xDBC01001); emf.writeUINT(IMAGE_TYPE_BITMAP); emf.writeUINT(0); // width emf.writeUINT(0); // height emf.writeUINT(0); // stride emf.writeUINT(0); // pixelformat emf.writeUINT(0x000001); // 01 00 00 00 for non-native images ImageGraphics2D.writeImage(image, "png", new Properties(), emf); } }