]> source.dussan.org Git - poi.git/commitdiff
#64716 - wmf display error
authorAndreas Beeker <kiwiwings@apache.org>
Fri, 27 Nov 2020 22:41:06 +0000 (22:41 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Fri, 27 Nov 2020 22:41:06 +0000 (22:41 +0000)
prepare binary raster operations

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1883882 13f79535-47bb-0310-9956-ffa450edef68

src/examples/src/org/apache/poi/examples/hwmf/ROP2Table.java [new file with mode: 0644]
src/examples/src/org/apache/poi/examples/hwmf/ROP3Table.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfROP2Composite.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfROP3Composite.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBinaryRasterOp.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfTernaryRasterOp.java
src/scratchpad/testcases/org/apache/poi/hwmf/TestRasterOp.java [new file with mode: 0644]

diff --git a/src/examples/src/org/apache/poi/examples/hwmf/ROP2Table.java b/src/examples/src/org/apache/poi/examples/hwmf/ROP2Table.java
new file mode 100644 (file)
index 0000000..a6c0b64
--- /dev/null
@@ -0,0 +1,106 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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 org.apache.poi.examples.hwmf;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hwmf.draw.HwmfROP2Composite;
+import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
+import org.apache.poi.util.Units;
+
+/**
+ * Generates an image table describing the various binary raster operations
+ *
+ * inspired from http://www.fengyuan.com/sample/samplech8.html
+ */
+public final class ROP2Table {
+    private static final Color[] COLORS = {
+            new Color(0,0,0),
+            new Color(128,0,0),
+            new Color(0,128,0),
+            new Color(128,128,0),
+            new Color(0,0,128),
+            new Color(128,0,128),
+            new Color(0,128,128),
+            new Color(192,192,192),
+            new Color(255,255,255),
+            new Color(128,128,128),
+            new Color(255,0,0),
+            new Color(0,255,0),
+            new Color(255,255,0),
+            new Color(0,0,255),
+            new Color(255,0,255),
+            new Color(0,255,255)
+    };
+
+    private ROP2Table() {
+    }
+
+    public static void main(String[] args) throws IOException {
+        int square = 800;
+        BufferedImage bi = new BufferedImage(square + 500, square, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = bi.createGraphics();
+        double space = 0.2;
+        double hbar = square / (COLORS.length + space);
+        double vbar = square / (COLORS.length + space);
+        double y = hbar * space;
+        double x = vbar * space;
+        double w = square - 2*x;
+        double h = square - 2*y;
+
+        Rectangle2D vrect = new Rectangle2D.Double(x, y, vbar * (1-space), h);
+        for (Color c : COLORS) {
+            g.setColor(c);
+            g.fill(vrect);
+            g.translate(vbar, 0);
+        }
+
+        g.setTransform(new AffineTransform());
+        g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) Units.pixelToPoints(hbar * 0.8)));
+
+        Composite comp = g.getComposite();
+
+        Rectangle2D hrect = new Rectangle2D.Double(x, y, w, hbar * (1-space));
+        int idx = 0;
+        for (HwmfBinaryRasterOp op : HwmfBinaryRasterOp.values()) {
+            g.setComposite(comp);
+            g.setColor(Color.BLACK);
+            g.drawString(op.name(), (int)(square+vbar), (int)(hbar*0.8));
+            g.setComposite(new HwmfROP2Composite(op));
+            g.setColor(Color.RED);
+            g.fill(hrect);
+            g.translate(0, hbar);
+            idx++;
+        }
+
+        g.dispose();
+        ImageIO.write(bi, "PNG", new File("rop2.png"));
+    }
+
+
+}
diff --git a/src/examples/src/org/apache/poi/examples/hwmf/ROP3Table.java b/src/examples/src/org/apache/poi/examples/hwmf/ROP3Table.java
new file mode 100644 (file)
index 0000000..13d4946
--- /dev/null
@@ -0,0 +1,168 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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 org.apache.poi.examples.hwmf;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.TexturePaint;
+import java.awt.font.TextLayout;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hwmf.draw.HwmfROP3Composite;
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
+
+/**
+ * Generates an image table describing the various ternary raster operations
+ *
+ * inspired from http://www.evmsoft.net/en/roptest.html
+ */
+public final class ROP3Table {
+    private ROP3Table() {
+    }
+
+    private static byte[] PATTERN = {
+        1, 0, 1, 0, 1, 0, 1, 0,
+        0, 1, 0, 1, 0, 1, 0, 1,
+        1, 0, 1, 1, 1, 0, 1, 1,
+        0, 1, 0, 1, 0, 1, 0, 1,
+        1, 0, 1, 0, 1, 0, 1, 0,
+        0, 1, 0, 1, 0, 1, 0, 1,
+        1, 0, 1, 1, 1, 0, 1, 1,
+        0, 1, 0, 1, 0, 1, 0, 1,
+    };
+
+    private static final HwmfTernaryRasterOp[] OPS = HwmfTernaryRasterOp.values();
+    private static final int COLS = 16;
+    private static final double BOX = 100, SCALE = 1, HEADER = 1.1;
+
+    private static final Rectangle2D RECT = new Rectangle2D.Double(0.05* BOX, 0.05* BOX, 0.90* BOX, 0.90* BOX);
+    private static final Shape CIRCLE_BIG = new Ellipse2D.Double(0.15* BOX, 0.15* BOX, 0.70* BOX, 0.70* BOX);
+    private static final Shape CIRCLE_SMALL = new Ellipse2D.Double(0.40* BOX, 0.40* BOX, 0.20* BOX, 0.20* BOX);
+    private static final Shape LABEL_BOX = new Rectangle.Double(0.06* BOX, 0.85* BOX, 0.88* BOX, 0.10* BOX);
+
+    private static final AlphaComposite SRC_OVER = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
+
+    private static final AffineTransform INIT_AT = AffineTransform.getScaleInstance(SCALE, SCALE);
+
+    public static void main(String[] args) throws IOException {
+        BufferedImage pattern = getPattern();
+        BufferedImage source = getSource();
+
+        BufferedImage dest = new BufferedImage(
+                (int)(BOX * COLS * SCALE),
+                (int)(BOX *(Math.max(OPS.length/COLS,1) + HEADER)* SCALE),
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = dest.createGraphics();
+
+        g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 10));
+
+        g.setTransform(INIT_AT);
+        g.setColor(Color.BLACK);
+
+        for (int i=0; i<3; i++) {
+            String str = new String[]{"Dest:","Source:","Pattern:"}[i];
+            TextLayout t = new TextLayout(str, g.getFont(), g.getFontRenderContext());
+            Rectangle2D b = t.getBounds();
+            g.drawString(str, (float)(((i*2+0.95)*BOX - b.getWidth())), (float)(0.55 * BOX));
+        }
+
+        g.translate(BOX, 0);
+        fillDest(g);
+        g.translate(2*BOX, 0);
+        g.drawImage(source, 0, 0, null);
+        g.translate(2*BOX, 0);
+        g.setPaint(new TexturePaint(pattern, RECT));
+        g.fill(RECT);
+
+        int idx=0;
+        for (HwmfTernaryRasterOp op : OPS) {
+            g.setTransform(INIT_AT);
+            g.translate(0, HEADER * BOX);
+            g.translate(BOX*(idx%COLS), BOX*(idx/COLS));
+
+            fillDest(g);
+            fillPattern(g, op, pattern, source);
+            fillLabel(g, op);
+            idx++;
+        }
+
+        g.dispose();
+        ImageIO.write(dest, "PNG", new File("rop3.png"));
+    }
+
+    private static BufferedImage getPattern() {
+        byte[] bw = { 0, -1 };
+        BufferedImage pattern = new BufferedImage(8, 8, BufferedImage.TYPE_BYTE_INDEXED, new IndexColorModel(1, 2, bw, bw, bw));
+        pattern.getRaster().setDataElements(0, 0, 8, 8, PATTERN);
+        return pattern;
+    }
+
+    private static BufferedImage getSource() {
+        BufferedImage checker = new BufferedImage((int) BOX, (int) BOX, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D cg = checker.createGraphics();
+        cg.setColor(Color.PINK);
+        cg.fill(new Rectangle2D.Double(0.05* BOX, 0.05* BOX, 0.90* BOX, 0.90* BOX));
+        cg.setColor(new Color(0xE6E6FA, false));
+        cg.fill(new Rectangle2D.Double(0.05* BOX, 0.05* BOX, 0.45* BOX, 0.45* BOX));
+        cg.fill(new Rectangle2D.Double(0.50* BOX, 0.50* BOX, 0.45* BOX, 0.45* BOX));
+        cg.dispose();
+        return checker;
+    }
+
+    private static void fillDest(Graphics2D g) {
+        g.setComposite(SRC_OVER);
+        g.setColor(Color.LIGHT_GRAY);
+        g.fill(RECT);
+        g.setColor(new Color(0xDAA520, false));
+        g.fill(CIRCLE_BIG);
+        g.setColor(Color.RED);
+        g.fill(CIRCLE_SMALL);
+    }
+
+    private static void fillPattern(Graphics2D g, HwmfTernaryRasterOp op, BufferedImage pattern, BufferedImage source) {
+        g.setComposite(new HwmfROP3Composite(g.getTransform(), RECT, op, pattern, Color.YELLOW, Color.BLUE));
+        g.setClip(RECT);
+        g.drawImage(source, 0, 0, null);
+        g.setClip(null);
+        g.setComposite(SRC_OVER);
+    }
+
+        private static void fillLabel(Graphics2D g, HwmfTernaryRasterOp op) {
+        g.setColor(Color.WHITE);
+        g.fill(LABEL_BOX);
+        g.setColor(Color.BLACK);
+
+        TextLayout t = new TextLayout(op.name(), g.getFont(), g.getFontRenderContext());
+        Rectangle2D b = t.getBounds();
+        g.drawString(op.name(), (float)((BOX -b.getWidth())/2.), (float)(0.94* BOX));
+
+    }
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfROP2Composite.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfROP2Composite.java
new file mode 100644 (file)
index 0000000..85cba23
--- /dev/null
@@ -0,0 +1,72 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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 org.apache.poi.hwmf.draw;
+
+import java.awt.Composite;
+import java.awt.CompositeContext;
+import java.awt.RenderingHints;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
+
+/**
+ * HWMFs Raster Operation for Binary arguments (Source / Destination)
+ */
+public class HwmfROP2Composite implements Composite {
+
+    private final HwmfBinaryRasterOp op;
+
+    public HwmfROP2Composite(HwmfBinaryRasterOp op) {
+        this.op = op;
+    }
+
+    @Override
+    public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) {
+        return new ROP2Context(op);
+    }
+
+    private static class ROP2Context implements CompositeContext {
+        private final HwmfBinaryRasterOp op;
+
+        public ROP2Context(HwmfBinaryRasterOp op) {
+            this.op = op;
+        }
+
+        @Override
+        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+            int w = Math.min(src.getWidth(), dstIn.getWidth());
+            int h = Math.min(src.getHeight(), dstIn.getHeight());
+
+            int[] srcPixels = new int[w];
+            int[] dstPixels = new int[w];
+
+            for (int y = 0; y < h; y++) {
+                src.getDataElements(0, y, w, 1, srcPixels);
+                dstIn.getDataElements(0, y, w, 1, dstPixels);
+                op.process(srcPixels, dstPixels);
+                dstOut.setDataElements(0, y, w, 1, dstPixels);
+            }
+        }
+
+        @Override
+        public void dispose() {
+        }
+    }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfROP3Composite.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfROP3Composite.java
new file mode 100644 (file)
index 0000000..6cac2a5
--- /dev/null
@@ -0,0 +1,125 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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 org.apache.poi.hwmf.draw;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.CompositeContext;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
+
+/**
+ * HWMFs Raster Operation for Ternary arguments (Source / Destination / Pattern)
+ */
+public class HwmfROP3Composite implements Composite {
+    private final HwmfTernaryRasterOp rop3;
+    private final byte[] mask;
+    private final int mask_width;
+    private final int mask_height;
+    private final int foreground;
+    private final int background;
+    private final Point2D startPnt;
+    private final boolean hasPattern;
+
+    public HwmfROP3Composite(AffineTransform at, Shape shape, HwmfTernaryRasterOp rop3, BufferedImage bitmap, Color background, Color foreground) {
+        this.rop3 = rop3;
+        if (bitmap == null) {
+            mask_width = 1;
+            mask_height = 1;
+            mask = new byte[]{1};
+        } else {
+            mask_width = bitmap.getWidth();
+            mask_height = bitmap.getHeight();
+            mask = new byte[mask_width * mask_height];
+            bitmap.getRaster().getDataElements(0, 0, mask_width, mask_height, mask);
+        }
+        this.background = background.getRGB();
+        this.foreground = foreground.getRGB();
+
+        Rectangle2D bnds = at.createTransformedShape(shape.getBounds2D()).getBounds2D();
+        startPnt = new Point2D.Double(bnds.getMinX(),bnds.getMinY());
+        hasPattern = rop3.calcCmd().contains("P");
+    }
+
+    @Override
+    public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) {
+        return new Rop3Context();
+    }
+
+    private class Rop3Context implements CompositeContext {
+        private final Deque<int[]> stack = new ArrayDeque<>();
+//        private Integer origOffsetX, origOffsetY;
+
+        @Override
+        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+            int w = Math.min(src.getWidth(), dstIn.getWidth());
+            int h = Math.min(src.getHeight(), dstIn.getHeight());
+
+            int startX = (int)startPnt.getX();
+            int startY = (int)startPnt.getY();
+            int offsetY = dstIn.getSampleModelTranslateY();
+            int offsetX = dstIn.getSampleModelTranslateX();
+
+            final int[] srcPixels = new int[w];
+            final int[] dstPixels = new int[w];
+            final int[] patPixels = hasPattern ? new int[w] : null;
+
+            for (int y = 0; y < h; y++) {
+                dstIn.getDataElements(0, y, w, 1, dstPixels);
+                src.getDataElements(0, y, w, 1, srcPixels);
+
+                fillPattern(patPixels, y, startX, startY, offsetX, offsetY);
+
+                rop3.process(stack, dstPixels, srcPixels, patPixels);
+                assert(stack.size() == 1);
+
+                int[] dstOutPixels = stack.pop();
+
+                dstOut.setDataElements(0, y, w, 1, dstOutPixels);
+            }
+        }
+
+        private void fillPattern(int[] patPixels, int y, int startX, int startY, int offsetX, int offsetY) {
+            if (patPixels != null) {
+                int offY2 = (startY+y+offsetY) % mask_height;
+                offY2 = (offY2 < 0) ? mask_height + offY2 : offY2;
+                int maskBase = offY2 * mask_width;
+                for (int i=0; i<patPixels.length; i++) {
+                    int offX2 = (startX+i+offsetX) % mask_width;
+                    offX2 = (offX2 < 0) ? mask_width + offX2 : offX2;
+                    patPixels[i] = mask[maskBase + offX2] == 0 ? background : foreground;
+                }
+            }
+        }
+
+        @Override
+        public void dispose() {
+        }
+    }
+}
index a4fb1cb353f19e045ecdb55706fd4b015e1439b9..4bb03ef3914c7f9edffb559fbc397b84350e3df4 100644 (file)
@@ -17,6 +17,9 @@
 
 package org.apache.poi.hwmf.record;
 
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+
 /**
  * The BinaryRasterOperation Enumeration section lists the binary raster-operation codes.
  * Rasteroperation codes define how metafile processing combines the bits from the selected
@@ -62,42 +65,44 @@ package org.apache.poi.hwmf.record;
  */
 public enum HwmfBinaryRasterOp {
     /** 0, Pixel is always 0 */
-    R2_BLACK(0x0001),
+    R2_BLACK(0x0001, HwmfBinaryRasterOp::R2_BLACK),
     /** DPon, Pixel is the inverse of the R2_MERGEPEN color. */
-    R2_NOTMERGEPEN(0x0002),
+    R2_NOTMERGEPEN(0x0002, HwmfBinaryRasterOp::R2_NOTMERGEPEN),
     /** DPna, Pixel is a combination of the screen color and the inverse of the pen color. */
-    R2_MASKNOTPEN(0x0003),
+    R2_MASKNOTPEN(0x0003, HwmfBinaryRasterOp::R2_MASKNOTPEN),
     /** Pn, Pixel is the inverse of the pen color. */
-    R2_NOTCOPYPEN(0x0004),
+    R2_NOTCOPYPEN(0x0004, HwmfBinaryRasterOp::R2_NOTCOPYPEN),
     /** PDna, Pixel is a combination of the colors common to both the pen and the inverse of the screen. */
-    R2_MASKPENNOT(0x0005),
+    R2_MASKPENNOT(0x0005, HwmfBinaryRasterOp::R2_MASKPENNOT),
     /** Dn, Pixel is the inverse of the screen color. */
-    R2_NOT(0x0006),
+    R2_NOT(0x0006, HwmfBinaryRasterOp::R2_NOT),
     /** DPx, Pixel is a combination of the colors in the pen or in the screen, but not in both. */
-    R2_XORPEN(0x0007),
+    R2_XORPEN(0x0007, HwmfBinaryRasterOp::R2_XORPEN),
     /** DPan, Pixel is the inverse of the R2_MASKPEN color. */
-    R2_NOTMASKPEN(0x0008),
+    R2_NOTMASKPEN(0x0008, HwmfBinaryRasterOp::R2_NOTMASKPEN),
     /** DPa, Pixel is a combination of the colors common to both the pen and the screen. */
-    R2_MASKPEN(0x0009),
+    R2_MASKPEN(0x0009, HwmfBinaryRasterOp::R2_MASKPEN),
     /** DPxn, Pixel is the inverse of the R2_XORPEN color. */
-    R2_NOTXORPEN(0x000A),
+    R2_NOTXORPEN(0x000A, HwmfBinaryRasterOp::R2_NOTXORPEN),
     /** D, Pixel remains unchanged. */
-    R2_NOP(0x000B),
+    R2_NOP(0x000B, HwmfBinaryRasterOp::R2_NOP),
     /** DPno, Pixel is a combination of the colors common to both the screen and the inverse of the pen. */
-    R2_MERGENOTPEN(0x000C),
+    R2_MERGENOTPEN(0x000C, HwmfBinaryRasterOp::R2_MERGENOTPEN),
     /** P, Pixel is the pen color. */
-    R2_COPYPEN(0x000D),
+    R2_COPYPEN(0x000D, HwmfBinaryRasterOp::R2_COPYPEN),
     /** PDno, Pixel is a combination of the pen color and the inverse of the screen color.*/
-    R2_MERGEPENNOT(0x000E),
+    R2_MERGEPENNOT(0x000E, HwmfBinaryRasterOp::R2_MERGEPENNOT),
     /** DPo, Pixel is a combination of the pen color and the screen color. */
-    R2_MERGEPEN(0x000F),
+    R2_MERGEPEN(0x000F, HwmfBinaryRasterOp::R2_MERGEPEN),
     /** 1, Pixel is always 1 */
-    R2_WHITE(0x0010);
+    R2_WHITE(0x0010, HwmfBinaryRasterOp::R2_WHITE);
 
-    int opIndex;
+    public int opIndex;
+    private BiConsumer<int[],int[]> op;
 
-    HwmfBinaryRasterOp(int opIndex) {
-        this.opIndex=opIndex;
+    HwmfBinaryRasterOp(int opIndex, BiConsumer<int[],int[]> op) {
+        this.opIndex = opIndex;
+        this.op = op;
     }
 
     public static HwmfBinaryRasterOp valueOf(int opIndex) {
@@ -109,4 +114,111 @@ public enum HwmfBinaryRasterOp {
         return null;
     }
 
+    public void process(int[] srcPixels, int[] dstPixels) {
+        op.accept(srcPixels, dstPixels);
+    }
+
+    /** 0, Pixel is always 0 */
+    private static void R2_BLACK(int[] srcPixels, int[] dstPixels) {
+        Arrays.fill(dstPixels, 0xFF000000);
+    }
+
+    /** DPon, Pixel is the inverse of the R2_MERGEPEN color */
+    private static void R2_NOTMERGEPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | (~(dstPixels[x] | srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** DPna, Pixel is a combination of the screen color and the inverse of the pen color. */
+    private static void R2_MASKNOTPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((dstPixels[x] & ~srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** Pn, Pixel is the inverse of the pen color. */
+    private static void R2_NOTCOPYPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | (~srcPixels[x] & 0x00FFFFFF);
+        }
+    }
+
+    /** PDna, Pixel is a combination of the colors common to both the pen and the inverse of the screen. */
+    private static void R2_MASKPENNOT(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((srcPixels[x] & ~dstPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** Dn, Pixel is the inverse of the screen color. */
+    private static void R2_NOT(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | (~dstPixels[x] & 0x00FFFFFF);
+        }
+    }
+
+    /** DPx, Pixel is a combination of the colors in the pen or in the screen, but not in both. */
+    private static void R2_XORPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((dstPixels[x] ^ srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** DPan, Pixel is the inverse of the R2_MASKPEN color. */
+    private static void R2_NOTMASKPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | (~(dstPixels[x] & srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** DPa, Pixel is a combination of the colors common to both the pen and the screen. */
+    private static void R2_MASKPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((dstPixels[x] & srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** DPxn, Pixel is the inverse of the R2_XORPEN color. */
+    private static void R2_NOTXORPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | (~(dstPixels[x] ^ srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** D, Pixel remains unchanged. */
+    private static void R2_NOP(int[] srcPixels, int[] dstPixels) {
+    }
+
+    /** DPno, Pixel is a combination of the colors common to both the screen and the inverse of the pen. */
+    private static void R2_MERGENOTPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((dstPixels[x] | ~srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** P, Pixel is the pen color. */
+    private static void R2_COPYPEN(int[] srcPixels, int[] dstPixels) {
+        // TODO: keep targets alpha?
+        System.arraycopy(srcPixels, 0, dstPixels, 0, srcPixels.length);
+    }
+
+    /** PDno, Pixel is a combination of the pen color and the inverse of the screen color. */
+    private static void R2_MERGEPENNOT(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((srcPixels[x] & ~dstPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** DPo, Pixel is a combination of the pen color and the screen color. */
+    private static void R2_MERGEPEN(int[] srcPixels, int[] dstPixels) {
+        for (int x = 0; x < srcPixels.length; x++) {
+            dstPixels[x] = (dstPixels[x] & 0xFF000000) | ((dstPixels[x] | srcPixels[x]) & 0x00FFFFFF);
+        }
+    }
+
+    /** 1, Pixel is always 1 */
+    private static void R2_WHITE(int[] srcPixels, int[] dstPixels) {
+        Arrays.fill(dstPixels, 0xFFFFFFFF);
+    }
 }
index 667b76f568bfff745a952bf94ce0f7431d04d631..a92c3da9812838ca58fab6e4a250c771e0d664ff 100644 (file)
@@ -66,7 +66,7 @@ public class HwmfFill {
          */
         byte[] getBMPData();
     }
-    
+
     /**
      * The ColorUsage Enumeration (a 16-bit unsigned integer) specifies whether a color table
      * exists in a device-independent bitmap (DIB) and how to interpret its values,
@@ -100,13 +100,13 @@ public class HwmfFill {
             return null;
         }
     }
-    
-    
+
+
     /**
      * The META_FILLREGION record fills a region using a specified brush.
      */
     public static class WmfFillRegion implements HwmfRecord {
-        
+
         /**
          * A 16-bit unsigned integer used to index into the WMF Object Table to get
          * the region to be filled.
@@ -118,12 +118,12 @@ public class HwmfFill {
          * brush to use for filling the region.
          */
         protected int brushIndex;
-        
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.fillRegion;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             regionIndex = leis.readUShort();
@@ -135,7 +135,7 @@ public class HwmfFill {
         public void draw(HwmfGraphics ctx) {
             ctx.applyObjectTableEntry(regionIndex);
             ctx.applyObjectTableEntry(brushIndex);
-            
+
             Shape region = ctx.getProperties().getRegion();
             if (region != null) {
                 ctx.fill(region);
@@ -164,7 +164,7 @@ public class HwmfFill {
      * defined in the playback device context.
      */
     public static class WmfPaintRegion implements HwmfRecord {
-        
+
         /**
          * A 16-bit unsigned integer used to index into the WMF Object Table to get
          * the region to be painted.
@@ -174,7 +174,7 @@ public class HwmfFill {
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.paintRegion;
         }
-        
+
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             regionIndex = leis.readUShort();
             return LittleEndianConsts.SHORT_SIZE;
@@ -187,7 +187,7 @@ public class HwmfFill {
             Shape region = ctx.getProperties().getRegion();
             if (region != null) {
                 ctx.fill(region);
-            }        
+            }
         }
 
         public int getRegionIndex() {
@@ -199,14 +199,14 @@ public class HwmfFill {
             return GenericRecordUtil.getGenericProperties("regionIndex", this::getRegionIndex);
         }
     }
-    
-    
+
+
     /**
      * The META_FLOODFILL record fills an area of the output surface with the brush that
      * is defined in the playback device context.
      */
     public static class WmfFloodFill implements HwmfRecord {
-        
+
         /** A 32-bit ColorRef Object that defines the color value. */
         protected final HwmfColorRef colorRef = new HwmfColorRef();
 
@@ -217,7 +217,7 @@ public class HwmfFill {
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.floodFill;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             int size = colorRef.init(leis);
@@ -227,7 +227,7 @@ public class HwmfFill {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            
+
         }
 
         public HwmfColorRef getColorRef() {
@@ -287,12 +287,12 @@ public class HwmfFill {
          * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
          */
         protected HwmfPolyfillMode polyFillMode;
-        
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setPolyFillMode;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             polyFillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
@@ -341,12 +341,12 @@ public class HwmfFill {
         }
 
         protected HwmfFloodFillMode mode;
-        
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.extFloodFill;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             // A 16-bit unsigned integer that defines the fill operation to be performed. This
@@ -357,7 +357,7 @@ public class HwmfFill {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            
+
         }
 
         public HwmfFloodFillMode getMode() {
@@ -374,18 +374,18 @@ public class HwmfFill {
      * The META_INVERTREGION record draws a region in which the colors are inverted.
      */
     public static class WmfInvertRegion implements HwmfRecord {
-        
+
         /**
          * A 16-bit unsigned integer used to index into the WMF Object Table to get
          * the region to be inverted.
          */
         private int regionIndex;
-        
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.invertRegion;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             regionIndex = leis.readUShort();
@@ -394,7 +394,7 @@ public class HwmfFill {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            
+
         }
 
         public int getRegionIndex() {
@@ -406,7 +406,7 @@ public class HwmfFill {
             return GenericRecordUtil.getGenericProperties("regionIndex", this::getRegionIndex);
         }
     }
-    
+
 
     /**
      * The META_PATBLT record paints a specified rectangle using the brush that is defined in the playback
@@ -414,20 +414,20 @@ public class HwmfFill {
      * raster operation.
      */
     public static class WmfPatBlt implements HwmfRecord {
-        
+
         /**
          * A 32-bit unsigned integer that defines the raster operation code.
          * This code MUST be one of the values in the Ternary Raster Operation enumeration table.
          */
         private HwmfTernaryRasterOp rasterOperation;
-        
+
         private final Rectangle2D bounds = new Rectangle2D.Double();
 
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.patBlt;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             rasterOperation = readRasterOperation(leis);
@@ -436,7 +436,7 @@ public class HwmfFill {
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            
+
         }
 
         public HwmfTernaryRasterOp getRasterOperation() {
@@ -469,7 +469,7 @@ public class HwmfFill {
     public static class WmfStretchBlt implements HwmfRecord {
         /**
          * A 32-bit unsigned integer that defines how the source pixels, the current brush
-         * in the playback device context, and the destination pixels are to be combined to form the new 
+         * in the playback device context, and the destination pixels are to be combined to form the new
          * image. This code MUST be one of the values in the Ternary Raster Operation Enumeration
          */
         protected HwmfTernaryRasterOp rasterOperation;
@@ -485,13 +485,13 @@ public class HwmfFill {
          * This object MUST be specified, even if the raster operation does not require a source.
          */
         protected HwmfBitmap16 target;
-        
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.stretchBlt;
         }
-        
-        
+
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             final boolean hasBitmap = hasBitmap(recordSize, recordFunction);
@@ -513,13 +513,13 @@ public class HwmfFill {
                 target = new HwmfBitmap16();
                 size += target.init(leis);
             }
-            
+
             return size;
         }
 
         @Override
         public void draw(HwmfGraphics ctx) {
-            
+
         }
 
         @Override
@@ -582,17 +582,17 @@ public class HwmfFill {
         protected final Rectangle2D dstBounds = new Rectangle2D.Double();
 
         /**
-         * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the 
+         * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the
          * source of the color data.
          */
         protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
-        
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.stretchDib;
         }
-        
-        
+
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             rasterOperation = readRasterOperation(leis);
@@ -606,7 +606,7 @@ public class HwmfFill {
             size += bitmap.init(leis, (int)(recordSize-6-size));
 
             return size;
-        }        
+        }
 
         @Override
         public void draw(HwmfGraphics ctx) {
@@ -666,14 +666,14 @@ public class HwmfFill {
             );
         }
     }
-    
+
     public static class WmfBitBlt extends WmfStretchBlt {
 
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.bitBlt;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             final boolean hasBitmap = hasBitmap(recordSize/2, recordFunction);
@@ -698,7 +698,7 @@ public class HwmfFill {
             }
 
             srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
-            
+
             return size;
         }
     }
@@ -715,7 +715,7 @@ public class HwmfFill {
          * A 16-bit unsigned integer that defines whether the Colors field of the
          * DIB contains explicit RGB values or indexes into a palette.
          */
-        private ColorUsage colorUsage;  
+        private ColorUsage colorUsage;
         /**
          * A 16-bit unsigned integer that defines the number of scan lines in the source.
          */
@@ -723,7 +723,7 @@ public class HwmfFill {
         /**
          * A 16-bit unsigned integer that defines the starting scan line in the source.
          */
-        private int startScan;  
+        private int startScan;
 
         /** the source rectangle */
         protected final Rectangle2D srcBounds = new Rectangle2D.Double();
@@ -734,14 +734,14 @@ public class HwmfFill {
         /**
          * A variable-sized DeviceIndependentBitmap Object that is the source of the color data.
          */
-        private HwmfBitmapDib dib;        
-        
-        
+        private HwmfBitmapDib dib;
+
+
         @Override
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.setDibToDev;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             colorUsage = ColorUsage.valueOf(leis.readUShort());
@@ -761,16 +761,16 @@ public class HwmfFill {
             srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
 
             return size;
-        }        
+        }
 
         @Override
         public void draw(HwmfGraphics ctx) {
             ctx.addObjectTableEntry(this);
         }
-        
+
         @Override
         public void applyObject(HwmfGraphics ctx) {
-            
+
         }
 
         @Override
@@ -822,7 +822,7 @@ public class HwmfFill {
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.dibBitBlt;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             final boolean hasBitmap = hasBitmap(recordSize/2, recordFunction);
@@ -885,7 +885,7 @@ public class HwmfFill {
         public HwmfRecordType getWmfRecordType() {
             return HwmfRecordType.dibStretchBlt;
         }
-        
+
         @Override
         public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
             final boolean hasBitmap = hasBitmap(recordSize, recordFunction);
@@ -905,7 +905,7 @@ public class HwmfFill {
                 target = new HwmfBitmapDib();
                 size += target.init(leis, (int)(recordSize-6-size));
             }
-            
+
             return size;
         }
 
@@ -983,7 +983,7 @@ public class HwmfFill {
         int rasterOpIndex = leis.readUShort();
 
         HwmfTernaryRasterOp rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
-        assert(rasterOperation != null && rasterOpCode == rasterOperation.opCode);
+        assert(rasterOperation != null && rasterOpCode == rasterOperation.getOpCode());
         return rasterOperation;
     }
 }
index 69afe0b44fc5bd0b631ac21352f02c6d95d0b0e9..f8e410633ca94632cfa5c564c0c33df323107c41 100644 (file)
@@ -17,6 +17,9 @@
 
 package org.apache.poi.hwmf.record;
 
+import java.util.Arrays;
+import java.util.Deque;
+
 /**
  * Each ternary raster operation code represents a Boolean operation in which the values of the pixels in
  * the source, the selected brush, and the destination are combined. Following are the three operands
@@ -41,17 +44,17 @@ package org.apache.poi.hwmf.record;
  * All Boolean operations are presented in reverse Polish notation. For example, the following operation
  * replaces the values of the pixels in the destination bitmap with a combination of the pixel values of the
  * source and brush: PSo.
- * 
+ *
  * The following operation combines the values of the pixels in the source and brush with the pixel values
  * of the destination bitmap: DPSoo (there are alternative spellings of some functions, so although a
  * particular spelling MAY NOT be listed in the enumeration, an equivalent form SHOULD be).
- * 
+ *
  * Each raster operation code is a 32-bit integer whose high-order word is a Boolean operation index and
  * whose low-order word is the operation code. The 16-bit operation index is a zero-extended, 8-bit
  * value that represents the result of the Boolean operation on predefined brush, source, and destination
  * values. For example, the operation indexes for the PSo and DPSoo operations are shown in the
  * following list.
- * 
+ *
  * <table>
  * <tr><th>P</th><th>S</th><th>D</th><th>DPo</th><th>DPan</th></tr>
  * <tr><td>0</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>
@@ -63,304 +66,338 @@ package org.apache.poi.hwmf.record;
  * <tr><td>1</td><td>1</td><td>0</td><td>1</td><td>1</td></tr>
  * <tr><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>
  * </table>
- * 
+ *
  * The operation indexes are determined by reading the binary values in a column of the table from the
  * bottom up. For example, in the PSo column, the binary value is 11111100, which is equivalent to 00FC
  * (hexadecimal is implicit for these values), which is the operation index for PSo.
- * 
+ *
  * Using this method, DPSoo can be seen to have the operation index 00FE. Operation indexes define the
  * locations of corresponding raster operation codes in the preceding enumeration. The PSo operation is
  * in line 252 (0x00FC) of the enumeration; DPSoo is in line 254 (0x00FE).
- * 
+ *
  * The most commonly used raster operations have been given explicit enumeration names, which
  * SHOULD be used; examples are PATCOPY and WHITENESS.
- * 
+ *
  * When the source and destination bitmaps are monochrome, a bit value of 0 represents a black pixel
  * and a bit value of 1 represents a white pixel. When the source and the destination bitmaps are color,
  * those colors are represented with red green blue (RGB) values.
  */
+@SuppressWarnings("unused")
 public enum HwmfTernaryRasterOp {
-    BLACKNESS(0x0000,0x0042,"0"),
-    DPSOON(0x0001,0x0289,"DPSoon"),
-    DPSONA(0x0002,0x0C89,"DPSona"),
-    PSON(0x0003,0x00AA,"PSon"),
-    SDPONA(0x0004,0x0C88,"SDPona"),
-    DPON(0x0005,0x00A9,"DPon"),
-    PDSXNON(0x0006,0x0865,"PDSxnon"),
-    PDSAON(0x0007,0x02C5,"PDSaon"),
-    SDPNAA(0x0008,0x0F08,"SDPnaa"),
-    PDSXON(0x0009,0x0245,"PDSxon"),
-    DPNA(0x000A,0x0329,"DPna"),
-    PSDNAON(0x000B,0x0B2A,"PSDnaon"),
-    SPNA(0x000C,0x0324,"SPna"),
-    PDSNAON(0x000D,0x0B25,"PDSnaon"),
-    PDSONON(0x000E,0x08A5,"PDSonon"),
-    PN(0x000F,0x0001,"Pn"),
-    PDSONA(0x0010,0x0C85,"PDSona"),
-    NOTSRCERASE(0x0011,0x00A6,"DSon"),
-    SDPXNON(0x0012,0x0868,"SDPxnon"),
-    SDPAON(0x0013,0x02C8,"SDPaon"),
-    DPSXNON(0x0014,0x0869,"DPSxnon"),
-    DPSAON(0x0015,0x02C9,"DPSaon"),
-    PSDPSANAXX(0x0016,0x5CCA,"PSDPSanaxx"),
-    SSPXDSXAXN(0x0017,0x1D54,"SSPxDSxaxn"),
-    SPXPDXA(0x0018,0x0D59,"SPxPDxa"),
-    SDPSANAXN(0x0019,0x1CC8,"SDPSanaxn"),
-    PDSPAOX(0x001A,0x06C5,"PDSPaox"),
-    SDPSXAXN(0x001B,0x0768,"SDPSxaxn"),
-    PSDPAOX(0x001C,0x06CA,"PSDPaox"),
-    DSPDXAXN(0x001D,0x0766,"DSPDxaxn"),
-    PDSOX(0x001E,0x01A5,"PDSox"),
-    PDSOAN(0x001F,0x0385,"PDSoan"),
-    DPSNAA(0x0020,0x0F09,"DPSnaa"),
-    SDPXON(0x0021,0x0248,"SDPxon"),
-    DSNA(0x0022,0x0326,"DSna"),
-    SPDNAON(0x0023,0x0B24,"SPDnaon"),
-    SPXDSXA(0x0024,0x0D55,"SPxDSxa"),
-    PDSPANAXN(0x0025,0x1CC5,"PDSPanaxn"),
-    SDPSAOX(0x0026,0x06C8,"SDPSaox"),
-    SDPSXNOX(0x0027,0x1868,"SDPSxnox"),
-    DPSXA(0x0028,0x0369,"DPSxa"),
-    PSDPSAOXXN(0x0029,0x16CA,"PSDPSaoxxn"),
-    DPSANA(0x002A,0x0CC9,"DPSana"),
-    SSPXPDXAXN(0x002B,0x1D58,"SSPxPDxaxn"),
-    SPDSOAX(0x002C,0x0784,"SPDSoax"),
-    PSDNOX(0x002D,0x060A,"PSDnox"),
-    PSDPXOX(0x002E,0x064A,"PSDPxox"),
-    PSDNOAN(0x002F,0x0E2A,"PSDnoan"),
-    PSNA(0x0030,0x032A,"PSna"),
-    SDPNAON(0x0031,0x0B28,"SDPnaon"),
-    SDPSOOX(0x0032,0x0688,"SDPSoox"),
-    NOTSRCCOPY(0x0033,0x0008,"Sn"),
-    SPDSAOX(0x0034,0x06C4,"SPDSaox"),
-    SPDSXNOX(0x0035,0x1864,"SPDSxnox"),
-    SDPOX(0x0036,0x01A8,"SDPox"),
-    SDPOAN(0x0037,0x0388,"SDPoan"),
-    PSDPOAX(0x0038,0x078A,"PSDPoax"),
-    SPDNOX(0x0390,0x604,"SPDnox"),
-    SPDSXOX(0x003A,0x0644,"SPDSxox"),
-    SPDNOAN(0x003B,0x0E24,"SPDnoan"),
-    PSX(0x003C,0x004A,"PSx"),
-    SPDSONOX(0x003D,0x18A4,"SPDSonox"),
-    SPDSNAOX(0x003E,0x1B24,"SPDSnaox"),
-    PSAN(0x003F,0x00EA,"PSan"),
-    PSDNAA(0x0040,0x0F0A,"PSDnaa"),
-    DPSXON(0x0041,0x0249,"DPSxon"),
-    SDXPDXA(0x0042,0x0D5D,"SDxPDxa"),
-    SPDSANAXN(0x0043,0x1CC4,"SPDSanaxn"),
-    SRCERASE(0x0044,0x0328,"SDna"),
-    DPSNAON(0x0045,0x0B29,"DPSnaon"),
-    DSPDAOX(0x0046,0x06C6,"DSPDaox"),
-    PSDPXAXN(0x0047,0x076A,"PSDPxaxn"),
-    SDPXA(0x0048,0x0368,"SDPxa"),
-    PDSPDAOXXN(0x0049,0x16C5,"PDSPDaoxxn"),
-    DPSDOAX(0x004A,0x0789,"DPSDoax"),
-    PDSNOX(0x004B,0x0605,"PDSnox"),
-    SDPANA(0x004C,0x0CC8,"SDPana"),
-    SSPXDSXOXN(0x004D,0x1954,"SSPxDSxoxn"),
-    PDSPXOX(0x004E,0x0645,"PDSPxox"),
-    PDSNOAN(0x004F,0x0E25,"PDSnoan"),
-    PDNA(0x0050,0x0325,"PDna"),
-    DSPNAON(0x0051,0x0B26,"DSPnaon"),
-    DPSDAOX(0x0052,0x06C9,"DPSDaox"),
-    SPDSXAXN(0x0053,0x0764,"SPDSxaxn"),
-    DPSONON(0x0054,0x08A9,"DPSonon"),
-    DSTINVERT(0x0055,0x0009,"Dn"),
-    DPSOX(0x0056,0x01A9,"DPSox"),
-    DPSOAN(0x0005,0x70389,"DPSoan"),
-    PDSPOAX(0x0058,0x0785,"PDSPoax"),
-    DPSNOX(0x0059,0x0609,"DPSnox"),
-    PATINVERT(0x005A,0x0049,"DPx"),
-    DPSDONOX(0x005B,0x18A9,"DPSDonox"),
-    DPSDXOX(0x005C,0x0649,"DPSDxox"),
-    DPSNOAN(0x005D,0x0E29,"DPSnoan"),
-    DPSDNAOX(0x005E,0x1B29,"DPSDnaox"),
-    DPAN(0x005F,0x00E9,"DPan"),
-    PDSXA(0x0060,0x0365,"PDSxa"),
-    DSPDSAOXXN(0x0061,0x16C6,"DSPDSaoxxn"),
-    DSPDOAX(0x0062,0x0786,"DSPDoax"),
-    SDPNOX(0x0063,0x0608,"SDPnox"),
-    SDPSOAX(0x0064,0x0788,"SDPSoax"),
-    DSPNOX(0x0065,0x0606,"DSPnox"),
-    SRCINVERT(0x0066,0x0046,"DSx"),
-    SDPSONOX(0x0067,0x18A8,"SDPSonox"),
-    DSPDSONOXXN(0x0068,0x58A6,"DSPDSonoxxn"),
-    PDSXXN(0x0069,0x0145,"PDSxxn"),
-    DPSAX(0x006A,0x01E9,"DPSax"),
-    PSDPSOAXXN(0x006B,0x178A,"PSDPSoaxxn"),
-    SDPAX(0x006C,0x01E8,"SDPax"),
-    PDSPDOAXXN(0x006D,0x1785,"PDSPDoaxxn"),
-    SDPSNOAX(0x006E,0x1E28,"SDPSnoax"),
-    // PDXNAN(0x006F,0x0C65,"PDXnan"), // invalid combo
-    PDSANA(0x0070,0x0CC5,"PDSana"),
-    SSDXPDXAXN(0x0071,0x1D5C,"SSDxPDxaxn"),
-    SDPSXOX(0x0072,0x0648,"SDPSxox"),
-    SDPNOAN(0x0073,0x0E28,"SDPnoan"),
-    DSPDXOX(0x0074,0x0646,"DSPDxox"),
-    DSPNOAN(0x0075,0x0E26,"DSPnoan"),
-    SDPSNAOX(0x0076,0x1B28,"SDPSnaox"),
-    DSAN(0x0077,0x00E6,"DSan"),
-    PDSAX(0x0078,0x01E5,"PDSax"),
-    DSPDSOAXXN(0x0079,0x1786,"DSPDSoaxxn"),
-    DPSDNOAX(0x007A,0x1E29,"DPSDnoax"),
-    SDPXNAN(0x007B,0x0C68,"SDPxnan"),
-    SPDSNOAX(0x007C,0x1E24,"SPDSnoax"),
-    DPSXNAN(0x007D,0x0C69,"DPSxnan"),
-    SPXDSXO(0x007E,0x0955,"SPxDSxo"),
-    DPSAAN(0x007F,0x03C9,"DPSaan"),
-    DPSAA(0x0080,0x03E9,"DPSaa"),
-    SPXDSXON(0x0081,0x0975,"SPxDSxon"),
-    DPSXNA(0x0082,0x0C49,"DPSxna"),
-    SPDSNOAXN(0x0083,0x1E04,"SPDSnoaxn"),
-    SDPXNA(0x0084,0x0C48,"SDPxna"),
-    PDSPNOAXN(0x0085,0x1E05,"PDSPnoaxn"),
-    DSPDSOAXX(0x0086,0x17A6,"DSPDSoaxx"),
-    PDSAXN(0x0087,0x01C5,"PDSaxn"),
-    SRCAND(0x0088,0x00C6,"DSa"),
-    SDPSNAOXN(0x0089,0x1B08,"SDPSnaoxn"),
-    DSPNOA(0x008A,0x0E06,"DSPnoa"),
-    DSPDXOXN(0x008B,0x0666,"DSPDxoxn"),
-    SDPNOA(0x008C,0x0E08,"SDPnoa"),
-    SDPSXOXN(0x008D,0x0668,"SDPSxoxn"),
-    SSDXPDXAX(0x008E,0x1D7C,"SSDxPDxax"),
-    PDSANAN(0x008F,0x0CE5,"PDSanan"),
-    PDSXNA(0x0090,0x0C45,"PDSxna"),
-    SDPSNOAXN(0x0091,0x1E08,"SDPSnoaxn"),
-    DPSDPOAXX(0x0092,0x17A9,"DPSDPoaxx"),
-    SPDAXN(0x0093,0x01C4,"SPDaxn"),
-    PSDPSOAXX(0x0094,0x17AA,"PSDPSoaxx"),
-    DPSAXN(0x0095,0x01C9,"DPSaxn"),
-    DPSXX(0x0096,0x0169,"DPSxx"),
-    PSDPSONOXX(0x0097,0x588A,"PSDPSonoxx"),
-    SDPSONOXN(0x0098,0x1888,"SDPSonoxn"),
-    DSXN(0x0099,0x0066,"DSxn"),
-    DPSNAX(0x009A,0x0709,"DPSnax"),
-    SDPSOAXN(0x009B,0x07A8,"SDPSoaxn"),
-    SPDNAX(0x009C,0x0704,"SPDnax"),
-    DSPDOAXN(0x009D,0x07A6,"DSPDoaxn"),
-    DSPDSAOXX(0x009E,0x16E6,"DSPDSaoxx"),
-    PDSXAN(0x009F,0x0345,"PDSxan"),
-    DPA(0x00A0,0x00C9,"DPa"),
-    PDSPNAOXN(0x00A1,0x1B05,"PDSPnaoxn"),
-    DPSNOA(0x00A2,0x0E09,"DPSnoa"),
-    DPSDXOXN(0x00A3,0x0669,"DPSDxoxn"),
-    PDSPONOXN(0x00A4,0x1885,"PDSPonoxn"),
-    PDXN(0x00A5,0x0065,"PDxn"),
-    DSPNAX(0x00A6,0x0706,"DSPnax"),
-    PDSPOAXN(0x00A7,0x07A5,"PDSPoaxn"),
-    DPSOA(0x00A8,0x03A9,"DPSoa"),
-    DPSOXN(0x00A9,0x0189,"DPSoxn"),
-    D(0x00AA,0x0029,"D"),
-    DPSONO(0x00AB,0x0889,"DPSono"),
-    SPDSXAX(0x00AC,0x0744,"SPDSxax"),
-    DPSDAOXN(0x00AD,0x06E9,"DPSDaoxn"),
-    DSPNAO(0x00AE,0x0B06,"DSPnao"),
-    DPNO(0x00AF,0x0229,"DPno"),
-    PDSNOA(0x00B0,0x0E05,"PDSnoa"),
-    PDSPXOXN(0x00B1,0x0665,"PDSPxoxn"),
-    SSPXDSXOX(0x00B2,0x1974,"SSPxDSxox"),
-    SDPANAN(0x00B3,0x0CE8,"SDPanan"),
-    PSDNAX(0x00B4,0x070A,"PSDnax"),
-    DPSDOAXN(0x00B5,0x07A9,"DPSDoaxn"),
-    DPSDPAOXX(0x00B6,0x16E9,"DPSDPaoxx"),
-    SDPXAN(0x00B7,0x0348,"SDPxan"),
-    PSDPXAX(0x00B8,0x074A,"PSDPxax"),
-    DSPDAOXN(0x00B9,0x06E6,"DSPDaoxn"),
-    DPSNAO(0x00BA,0x0B09,"DPSnao"),
-    MERGEPAINT(0x00BB,0x0226,"DSno"),
-    SPDSANAX(0x00BC,0x1CE4,"SPDSanax"),
-    SDXPDXAN(0x00BD,0x0D7D,"SDxPDxan"),
-    DPSXO(0x00BE,0x0269,"DPSxo"),
-    DPSANO(0x00BF,0x08C9,"DPSano"),
-    MERGECOPY(0x00C0,0x00CA,"PSa"),
-    SPDSNAOXN(0x00C1,0x1B04,"SPDSnaoxn"),
-    SPDSONOXN(0x00C2,0x1884,"SPDSonoxn"),
-    PSXN(0x00C3,0x006A,"PSxn"),
-    SPDNOA(0x00C4,0x0E04,"SPDnoa"),
-    SPDSXOXN(0x00C5,0x0664,"SPDSxoxn"),
-    SDPNAX(0x00C6,0x0708,"SDPnax"),
-    PSDPOAXN(0x00C7,0x07AA,"PSDPoaxn"),
-    SDPOA(0x00C8,0x03A8,"SDPoa"),
-    SPDOXN(0x00C9,0x0184,"SPDoxn"),
-    DPSDXAX(0x00CA,0x0749,"DPSDxax"),
-    SPDSAOXN(0x00CB,0x06E4,"SPDSaoxn"),
-    SRCCOPY(0x00CC,0x0020,"S"),
-    SDPONO(0x00CD,0x0888,"SDPono"),
-    SDPNAO(0x00CE,0x0B08,"SDPnao"),
-    SPNO(0x00CF,0x0224,"SPno"),
-    PSDNOA(0x00D0,0x0E0A,"PSDnoa"),
-    PSDPXOXN(0x00D1,0x066A,"PSDPxoxn"),
-    PDSNAX(0x00D2,0x0705,"PDSnax"),
-    SPDSOAXN(0x00D3,0x07A4,"SPDSoaxn"),
-    SSPXPDXAX(0x00D4,0x1D78,"SSPxPDxax"),
-    DPSANAN(0x00D5,0x0CE9,"DPSanan"),
-    PSDPSAOXX(0x00D6,0x16EA,"PSDPSaoxx"),
-    DPSXAN(0x00D7,0x0349,"DPSxan"),
-    PDSPXAX(0x00D8,0x0745,"PDSPxax"),
-    SDPSAOXN(0x00D9,0x06E8,"SDPSaoxn"),
-    DPSDANAX(0x00DA,0x1CE9,"DPSDanax"),
-    SPXDSXAN(0x00DB,0x0D75,"SPxDSxan"),
-    SPDNAO(0x00DC,0x0B04,"SPDnao"),
-    SDNO(0x00DD,0x0228,"SDno"),
-    SDPXO(0x00DE,0x0268,"SDPxo"),
-    SDPANO(0x00DF,0x08C8,"SDPano"),
-    PDSOA(0x00E0,0x03A5,"PDSoa"),
-    PDSOXN(0x00E1,0x0185,"PDSoxn"),
-    DSPDXAX(0x00E2,0x0746,"DSPDxax"),
-    PSDPAOXN(0x00E3,0x06EA,"PSDPaoxn"),
-    SDPSXAX(0x00E4,0x0748,"SDPSxax"),
-    PDSPAOXN(0x00E5,0x06E5,"PDSPaoxn"),
-    SDPSANAX(0x00E6,0x1CE8,"SDPSanax"),
-    SPXPDXAN(0x00E7,0x0D79,"SPxPDxan"),
-    SSPXDSXAX(0x00E8,0x1D74,"SSPxDSxax"),
-    DSPDSANAXXN(0x00E9,0x5CE6,"DSPDSanaxxn"),
-    DPSAO(0x00EA,0x02E9,"DPSao"),
-    DPSXNO(0x00EB,0x0849,"DPSxno"),
-    SDPAO(0x00EC,0x02E8,"SDPao"),
-    SDPXNO(0x00ED,0x0848,"SDPxno"),
-    SRCPAINT(0x00EE,0x0086,"DSo"),
-    SDPNOO(0x00EF,0x0A08,"SDPnoo"),
-    PATCOPY(0x00F0,0x0021,"P"),
-    PDSONO(0x00F1,0x0885,"PDSono"),
-    PDSNAO(0x00F2,0x0B05,"PDSnao"),
-    PSNO(0x00F3,0x022A,"PSno"),
-    PSDNAO(0x00F4,0x0B0A,"PSDnao"),
-    PDNO(0x00F5,0x0225,"PDno"),
-    PDSXO(0x00F6,0x0265,"PDSxo"),
-    PDSANO(0x00F7,0x08C5,"PDSano"),
-    PDSAO(0x00F8,0x02E5,"PDSao"),
-    PDSXNO(0x00F9,0x0845,"PDSxno"),
-    DPO(0x00FA,0x0089,"DPo"),
-    PATPAINT(0x00FB,0x0A09,"DPSnoo"),
-    PSO(0x00FC,0x008A,"PSo"),
-    PSDNOO(0x00FD,0x0A0A,"PSDnoo"),
-    DPSOO(0x00FE,0x02A9,"DPSoo"),
-    WHITENESS(0x00FF,0x0062,"1");
-
-    int opIndex;
-    int opCode;
-    String opCmd;
-
-    HwmfTernaryRasterOp(int opIndex, int opCode, String opCmd) {
-        this.opIndex=opIndex;
-        this.opCode=opCode;
-        this.opCmd=opCmd;
+    /** Fills the destination rectangle with black */
+    BLACKNESS(0x00000042),
+    DPSOON(0x00010289),
+    DPSONA(0x00020C89),
+    PSON(0x000300AA),
+    SDPONA(0x00040C88),
+    DPON(0x000500A9),
+    PDSXNON(0x00060865),
+    PDSAON(0x000702C5),
+    SDPNAA(0x00080F08),
+    PDSXON(0x00090245),
+    DPNA(0x000A0329),
+    PSDNAON(0x000B0B2A),
+    SPNA(0x000C0324),
+    PDSNAON(0x000D0B25),
+    PDSONON(0x000E08A5),
+    PN(0x000F0001),
+    PDSONA(0x00100C85),
+    /** Fills the destination area with (not (Dst or Src)) */
+    NOTSRCERASE(0x001100A6),
+    SDPXNON(0x00120868),
+    SDPAON(0x001302C8),
+    DPSXNON(0x00140869),
+    DPSAON(0x001502C9),
+    PSDPSANAXX(0x00165CCA),
+    SSPXDSXAXN(0x00171D54),
+    SPXPDXA(0x00180D59),
+    SDPSANAXN(0x00191CC8),
+    PDSPAOX(0x001A06C5),
+    SDPSXAXN(0x001B0768),
+    PSDPAOX(0x001C06CA),
+    DSPDXAXN(0x001D0766),
+    PDSOX(0x001E01A5),
+    PDSOAN(0x001F0385),
+    DPSNAA(0x00200F09),
+    SDPXON(0x00210248),
+    DSNA(0x00220326),
+    SPDNAON(0x00230B24),
+    SPXDSXA(0x00240D55),
+    PDSPANAXN(0x00251CC5),
+    SDPSAOX(0x002606C8),
+    SDPSXNOX(0x00271868),
+    DPSXA(0x00280369),
+    PSDPSAOXXN(0x002916CA),
+    DPSANA(0x002A0CC9),
+    SSPXPDXAXN(0x002B1D58),
+    SPDSOAX(0x002C0784),
+    PSDNOX(0x002D060A),
+    PSDPXOX(0x002E064A),
+    PSDNOAN(0x002F0E2A),
+    PSNA(0x0030032A),
+    SDPNAON(0x00310B28),
+    SDPSOOX(0x00320688),
+    /** Fills the destination area with (not Src) */
+    NOTSRCCOPY(0x00330008),
+    SPDSAOX(0x003406C4),
+    SPDSXNOX(0x00351864),
+    SDPOX(0x003601A8),
+    SDPOAN(0x00370388),
+    PSDPOAX(0x0038078A),
+    SPDNOX(0x0390604),
+    SPDSXOX(0x003A0644),
+    SPDNOAN(0x003B0E24),
+    PSX(0x003C004A),
+    SPDSONOX(0x003D18A4),
+    SPDSNAOX(0x003E1B24),
+    PSAN(0x003F00EA),
+    PSDNAA(0x00400F0A),
+    DPSXON(0x00410249),
+    SDXPDXA(0x00420D5D),
+    SPDSANAXN(0x00431CC4),
+    /** Fills the destination area with ((not Dst) and Src) */
+    SRCERASE(0x00440328),
+    DPSNAON(0x00450B29),
+    DSPDAOX(0x004606C6),
+    PSDPXAXN(0x0047076A),
+    SDPXA(0x00480368),
+    PDSPDAOXXN(0x004916C5),
+    DPSDOAX(0x004A0789),
+    PDSNOX(0x004B0605),
+    SDPANA(0x004C0CC8),
+    SSPXDSXOXN(0x004D1954),
+    PDSPXOX(0x004E0645),
+    PDSNOAN(0x004F0E25),
+    PDNA(0x00500325),
+    DSPNAON(0x00510B26),
+    DPSDAOX(0x005206C9),
+    SPDSXAXN(0x00530764),
+    DPSONON(0x005408A9),
+    /** Inverts the colors of the destination area */
+    DSTINVERT(0x00550009),
+    DPSOX(0x005601A9),
+    DPSOAN(0x000570389),
+    PDSPOAX(0x00580785),
+    DPSNOX(0x00590609),
+    /** Fills the destination area with (Dst xor Pattern) */
+    PATINVERT(0x005A0049),
+    DPSDONOX(0x005B18A9),
+    DPSDXOX(0x005C0649),
+    DPSNOAN(0x005D0E29),
+    DPSDNAOX(0x005E1B29),
+    DPAN(0x005F00E9),
+    PDSXA(0x00600365),
+    DSPDSAOXXN(0x006116C6),
+    DSPDOAX(0x00620786),
+    SDPNOX(0x00630608),
+    SDPSOAX(0x00640788),
+    DSPNOX(0x00650606),
+    /** Fills the destination area with (Dst xor Src) */
+    SRCINVERT(0x00660046),
+    SDPSONOX(0x006718A8),
+    DSPDSONOXXN(0x006858A6),
+    PDSXXN(0x00690145),
+    DPSAX(0x006A01E9),
+    PSDPSOAXXN(0x006B178A),
+    SDPAX(0x006C01E8),
+    PDSPDOAXXN(0x006D1785),
+    SDPSNOAX(0x006E1E28),
+    PDSXNAN(0x006F0C65),
+    PDSANA(0x00700CC5),
+    SSDXPDXAXN(0x00711D5C),
+    SDPSXOX(0x00720648),
+    SDPNOAN(0x00730E28),
+    DSPDXOX(0x00740646),
+    DSPNOAN(0x00750E26),
+    SDPSNAOX(0x00761B28),
+    DSAN(0x007700E6),
+    PDSAX(0x007801E5),
+    DSPDSOAXXN(0x00791786),
+    DPSDNOAX(0x007A1E29),
+    SDPXNAN(0x007B0C68),
+    SPDSNOAX(0x007C1E24),
+    DPSXNAN(0x007D0C69),
+    SPXDSXO(0x007E0955),
+    DPSAAN(0x007F03C9),
+    DPSAA(0x008003E9),
+    SPXDSXON(0x00810975),
+    DPSXNA(0x00820C49),
+    SPDSNOAXN(0x00831E04),
+    SDPXNA(0x00840C48),
+    PDSPNOAXN(0x00851E05),
+    DSPDSOAXX(0x008617A6),
+    PDSAXN(0x008701C5),
+    /** Fills the destination area with (Dst and Src) */
+    SRCAND(0x008800C6),
+    SDPSNAOXN(0x00891B08),
+    DSPNOA(0x008A0E06),
+    DSPDXOXN(0x008B0666),
+    SDPNOA(0x008C0E08),
+    SDPSXOXN(0x008D0668),
+    SSDXPDXAX(0x008E1D7C),
+    PDSANAN(0x008F0CE5),
+    PDSXNA(0x00900C45),
+    SDPSNOAXN(0x00911E08),
+    DPSDPOAXX(0x009217A9),
+    SPDAXN(0x009301C4),
+    PSDPSOAXX(0x009417AA),
+    DPSAXN(0x009501C9),
+    DPSXX(0x00960169),
+    PSDPSONOXX(0x0097588A),
+    SDPSONOXN(0x00981888),
+    DSXN(0x00990066),
+    DPSNAX(0x009A0709),
+    SDPSOAXN(0x009B07A8),
+    SPDNAX(0x009C0704),
+    DSPDOAXN(0x009D07A6),
+    DSPDSAOXX(0x009E16E6),
+    PDSXAN(0x009F0345),
+    DPA(0x00A000C9),
+    PDSPNAOXN(0x00A11B05),
+    DPSNOA(0x00A20E09),
+    DPSDXOXN(0x00A30669),
+    PDSPONOXN(0x00A41885),
+    PDXN(0x00A50065),
+    DSPNAX(0x00A60706),
+    PDSPOAXN(0x00A707A5),
+    DPSOA(0x00A803A9),
+    DPSOXN(0x00A90189),
+    D(0x00AA0029),
+    DPSONO(0x00AB0889),
+    SPDSXAX(0x00AC0744),
+    DPSDAOXN(0x00AD06E9),
+    DSPNAO(0x00AE0B06),
+    DPNO(0x00AF0229),
+    PDSNOA(0x00B00E05),
+    PDSPXOXN(0x00B10665),
+    SSPXDSXOX(0x00B21974),
+    SDPANAN(0x00B30CE8),
+    PSDNAX(0x00B4070A),
+    DPSDOAXN(0x00B507A9),
+    DPSDPAOXX(0x00B616E9),
+    SDPXAN(0x00B70348),
+    PSDPXAX(0x00B8074A),
+    DSPDAOXN(0x00B906E6),
+    DPSNAO(0x00BA0B09),
+    /** Fills the destination area with (Dst or not Src) */
+    MERGEPAINT(0x00BB0226),
+    SPDSANAX(0x00BC1CE4),
+    SDXPDXAN(0x00BD0D7D),
+    DPSXO(0x00BE0269),
+    DPSANO(0x00BF08C9),
+    /** Fills the destination area with (Src and Pattern) */
+    MERGECOPY(0x00C000CA),
+    SPDSNAOXN(0x00C11B04),
+    SPDSONOXN(0x00C21884),
+    PSXN(0x00C3006A),
+    SPDNOA(0x00C40E04),
+    SPDSXOXN(0x00C50664),
+    SDPNAX(0x00C60708),
+    PSDPOAXN(0x00C707AA),
+    SDPOA(0x00C803A8),
+    SPDOXN(0x00C90184),
+    DPSDXAX(0x00CA0749),
+    SPDSAOXN(0x00CB06E4),
+    /** Fills the destination area with Src */
+    SRCCOPY(0x00CC0020),
+    SDPONO(0x00CD0888),
+    SDPNAO(0x00CE0B08),
+    SPNO(0x00CF0224),
+    PSDNOA(0x00D00E0A),
+    PSDPXOXN(0x00D1066A),
+    PDSNAX(0x00D20705),
+    SPDSOAXN(0x00D307A4),
+    SSPXPDXAX(0x00D41D78),
+    DPSANAN(0x00D50CE9),
+    PSDPSAOXX(0x00D616EA),
+    DPSXAN(0x00D70349),
+    PDSPXAX(0x00D80745),
+    SDPSAOXN(0x00D906E8),
+    DPSDANAX(0x00DA1CE9),
+    SPXDSXAN(0x00DB0D75),
+    SPDNAO(0x00DC0B04),
+    SDNO(0x00DD0228),
+    SDPXO(0x00DE0268),
+    SDPANO(0x00DF08C8),
+    PDSOA(0x00E003A5),
+    PDSOXN(0x00E10185),
+    DSPDXAX(0x00E20746),
+    PSDPAOXN(0x00E306EA),
+    SDPSXAX(0x00E40748),
+    PDSPAOXN(0x00E506E5),
+    SDPSANAX(0x00E61CE8),
+    SPXPDXAN(0x00E70D79),
+    SSPXDSXAX(0x00E81D74),
+    DSPDSANAXXN(0x00E95CE6),
+    DPSAO(0x00EA02E9),
+    DPSXNO(0x00EB0849),
+    SDPAO(0x00EC02E8),
+    SDPXNO(0x00ED0848),
+    /** Combines the colors of the source and the destination using the operator OR on each pixel */
+    SRCPAINT(0x00EE0086),
+    SDPNOO(0x00EF0A08),
+    /** Fills the destination area with (Pattern) */
+    PATCOPY(0x00F00021),
+    PDSONO(0x00F10885),
+    PDSNAO(0x00F20B05),
+    PSNO(0x00F3022A),
+    PSDNAO(0x00F40B0A),
+    PDNO(0x00F50225),
+    PDSXO(0x00F60265),
+    PDSANO(0x00F708C5),
+    PDSAO(0x00F802E5),
+    PDSXNO(0x00F90845),
+    DPO(0x00FA0089),
+    /** Fills the destination area with (Dst or (not Src) or Pattern) */
+    PATPAINT(0x00FB0A09),
+    PSO(0x00FC008A),
+    PSDNOO(0x00FD0A0A),
+    DPSOO(0x00FE02A9),
+    /** Fills the destination rectangle with white */
+    WHITENESS(0x00FF0062);
+
+
+    private static final String[] ARG_ORDER = {
+        "SSSSSS","PPPPPP","DDDDDD",null,
+        "SPDSPD","PDSPDS","DSPDSP",null,
+        "SDPSDP","DPSDPS","PSDPSD",null,
+        null,     null,    null,   null,
+        null,     null,    null,   null,
+        "SSP.DS", "SP.DS", null,   null,
+        "SSP.PD", "SP.PD", null,   null,
+        "SSD.PD", "SD.PD", null,   null
+    };
+
+    private static final String OPS = "nxoa";
+
+    public int opValue;
+
+    HwmfTernaryRasterOp(int opValue) {
+        this.opValue=opValue;
     }
 
     public static HwmfTernaryRasterOp valueOf(int opIndex) {
         for (HwmfTernaryRasterOp bb : HwmfTernaryRasterOp.values()) {
-            if (bb.opIndex == opIndex) {
+            if (bb.getOpIndex() == opIndex) {
                 return bb;
             }
         }
         return null;
     }
 
+    public int getOpIndex() {
+        return opValue >>> 16;
+    }
+
+    public int getOpCode() {
+        return opValue & 0xFF;
+    }
+
     public String describeCmd() {
         String[] stack = new String[10];
         int stackPnt = 0;
 
-        for (char c : opCmd.toCharArray()) {
+        for (char c : calcCmd().toCharArray()) {
             switch (c) {
                 case 'S':
                 case 'D':
@@ -395,4 +432,133 @@ public enum HwmfTernaryRasterOp {
 
         return stack[--stackPnt];
     }
-}
+
+
+    public String calcCmd() {
+        // taken from https://wiki.winehq.org/Ternary_Raster_Ops
+
+        // bit 0-4: Specify the order of arguments to the raster operation
+        String argOrder = ARG_ORDER[this.opValue & 0x001F];
+        assert(argOrder != null);
+
+        // The boolean operators, 1st (6-7 bit), 2nd (8-9 bit), 3rd (a-b bit), 4th (c-d bit), 5th (e-f bit)
+        int nbrOfOps = 0;
+        int[] opArr = new int[5];
+        for (int i=0, bit=6; i<opArr.length; i++, bit+=2) {
+            if ((opArr[i] = (this.opValue >>> bit) & 0x03) != 0) {
+                nbrOfOps = i+1;
+            }
+        }
+
+        StringBuilder sbArg = new StringBuilder(), sbOp = new StringBuilder();
+        sbArg.append(argOrder.charAt(0));
+        for (int opIdx=0,argIdx=1; opIdx < nbrOfOps; opIdx++) {
+            char opCh = OPS.charAt(opArr[opIdx]);
+            char ch = argOrder.charAt(argIdx);
+            sbOp.append(opCh);
+            if (ch == '.') {
+                sbArg.insert(argIdx, sbOp.charAt(0));
+                sbOp.deleteCharAt(0);
+            }
+            if (opCh == 'n') {
+                continue;
+            }
+            sbArg.append(ch == '.' ? argOrder.charAt(++argIdx) : ch);
+            argIdx++;
+        }
+        sbArg.append(sbOp);
+
+        // bit 5: Used to apply the NOT operator to the results of the other operations
+        // if 0, there are a ODD number of operations, even number otherwise
+        if ((nbrOfOps % 2) == ((this.opValue >>> 0x05) & 1)) {
+            sbArg.append('n');
+        }
+
+        String ret = sbArg.toString();
+        return ret.startsWith("DDx") ? "DDx".equals(ret) ? "0" : "1" : ret;
+    }
+
+    public void process(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        for (char op : calcCmd().toCharArray()) {
+            switch (op) {
+                case 'S': opS(stack, dst, src, pat); break;
+                case 'P': opP(stack, dst, src, pat); break;
+                case 'D': opD(stack, dst, src, pat); break;
+                case 'n': opN(stack, dst, src, pat); break;
+                case 'a': opA(stack, dst, src, pat); break;
+                case 'o': opO(stack, dst, src, pat); break;
+                case 'x': opX(stack, dst, src, pat); break;
+                case '1': op1(stack, dst, src, pat); break;
+                case '0': op0(stack, dst, src, pat); break;
+                default: throw new IllegalStateException();
+            }
+        }
+    }
+
+    private static void opS(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        stack.push(src);
+    }
+
+    private static void opP(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        stack.push(pat);
+    }
+
+    private static void opD(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        stack.push(dst);
+    }
+
+    private static void opN(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        int[] oper = checkClone(stack.pop(), dst, src, pat, true);
+        for (int i=0; i<oper.length; i++) {
+            oper[i] = (oper[i]&0xFF000000) | (~oper[i] & 0x00FFFFFF);
+        }
+        stack.push(oper);
+    }
+
+    private static void opA(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        int[] oper1 = checkClone(stack.pop(), dst, src, pat, true);
+        int[] oper2 = checkClone(stack.pop(), dst, src, pat, false);
+        for (int i=0; i<oper1.length; i++) {
+            oper1[i] = (oper1[i]&0xFF000000) | ((oper1[i] & oper2[i]) & 0x00FFFFFF);
+        }
+        stack.push(oper1);
+    }
+
+    private static void opO(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        int[] oper1 = checkClone(stack.pop(), dst, src, pat, true);
+        int[] oper2 = checkClone(stack.pop(), dst, src, pat, false);
+        for (int i=0; i<oper1.length; i++) {
+            oper1[i] = (oper1[i]&0xFF000000) | ((oper1[i] | oper2[i]) & 0x00FFFFFF);
+        }
+        stack.push(oper1);
+    }
+
+    private static void opX(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        int[] oper1 = checkClone(stack.pop(), dst, src, pat, true);
+        int[] oper2 = checkClone(stack.pop(), dst, src, pat, false);
+        for (int i=0; i<oper1.length; i++) {
+            oper1[i] = (oper1[i]&0xFF000000) | ((oper1[i] ^ oper2[i]) & 0x00FFFFFF);
+        }
+        stack.push(oper1);
+    }
+
+    private static void op1(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        int[] oper = new int[dst.length];
+        Arrays.fill(oper, 0xFFFFFFFF);
+        stack.push(oper);
+    }
+
+    private static void op0(Deque<int[]> stack, int[] dst, int[] src, int[] pat) {
+        int[] oper = new int[dst.length];
+        Arrays.fill(oper, 0xFF000000);
+        stack.push(oper);
+    }
+
+    private static int[] checkClone(int[] oper, int[] dst, int[] src, int[] pat, boolean force) {
+        if (force && (oper == src || oper == pat || oper == dst)) {
+            return oper.clone();
+        } else {
+            return oper;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/scratchpad/testcases/org/apache/poi/hwmf/TestRasterOp.java b/src/scratchpad/testcases/org/apache/poi/hwmf/TestRasterOp.java
new file mode 100644 (file)
index 0000000..4723f81
--- /dev/null
@@ -0,0 +1,35 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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 org.apache.poi.hwmf;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
+import org.junit.Test;
+
+public class TestRasterOp {
+    @Test
+    public void checkTertiaryCalcCmd() {
+        for (HwmfTernaryRasterOp op : HwmfTernaryRasterOp.values()) {
+            String cmd = op.calcCmd();
+            if (HwmfTernaryRasterOp.SSPXDSXOXN == op) {
+                assertEquals("SSPxDSxoxn", cmd);
+            }
+        }
+    }
+}