/*

   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.batik.ext.awt.image;

import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.PackedColorModel;
import java.awt.image.Raster;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

/**
 * This provides an implementation of all the composite rules in SVG.
 *
 * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a>
 * @version $Id: SVGComposite.java 1733416 2016-03-03 07:07:13Z gadams $
 */
public class SVGComposite
    implements Composite {

    public static final SVGComposite OVER
        = new SVGComposite(CompositeRule.OVER);

    public static final SVGComposite IN
        = new SVGComposite(CompositeRule.IN);

    public static final SVGComposite OUT
        = new SVGComposite(CompositeRule.OUT);

    public static final SVGComposite ATOP
        = new SVGComposite(CompositeRule.ATOP);

    public static final SVGComposite XOR
        = new SVGComposite(CompositeRule.XOR);

    public static final SVGComposite MULTIPLY
        = new SVGComposite(CompositeRule.MULTIPLY);

    public static final SVGComposite SCREEN
        = new SVGComposite(CompositeRule.SCREEN);

    public static final SVGComposite DARKEN
        = new SVGComposite(CompositeRule.DARKEN);

    public static final SVGComposite LIGHTEN
        = new SVGComposite(CompositeRule.LIGHTEN);


    CompositeRule rule;

    public CompositeRule getRule() { return rule; }

    public SVGComposite(CompositeRule rule) {
        this.rule = rule;
    }

    public boolean equals(Object o) {
        if (o instanceof SVGComposite) {
            SVGComposite svgc = (SVGComposite)o;
            return (svgc.getRule() == getRule());
        } else if (o instanceof AlphaComposite) {
            AlphaComposite ac = (AlphaComposite)o;
            switch (getRule().getRule()) {
            case CompositeRule.RULE_OVER:
                return (ac == AlphaComposite.SrcOver);
            case CompositeRule.RULE_IN:
                return (ac == AlphaComposite.SrcIn);
            case CompositeRule.RULE_OUT:
                return (ac == AlphaComposite.SrcOut);
            default:
                return false;
            }
        }
        return false;
    }

    public boolean is_INT_PACK(ColorModel cm) {
          // Check ColorModel is of type DirectColorModel
        if(!(cm instanceof PackedColorModel)) return false;

        PackedColorModel pcm = (PackedColorModel)cm;

        int [] masks = pcm.getMasks();

        // Check transfer type
        if(masks.length != 4) return false;

        if (masks[0] != 0x00ff0000) return false;
        if (masks[1] != 0x0000ff00) return false;
        if (masks[2] != 0x000000ff) return false;
        if (masks[3] != 0xff000000) return false;

        return true;
   }

    public CompositeContext createContext(ColorModel srcCM,
                                          ColorModel dstCM,
                                          RenderingHints hints) {
        if (false) {
            ColorSpace srcCS = srcCM.getColorSpace();
            ColorSpace dstCS = dstCM.getColorSpace();
            System.out.println("srcCS: " + srcCS);
            System.out.println("dstCS: " + dstCS);
            System.out.println
                ("lRGB: " + ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB));
            System.out.println
                ("sRGB: " + ColorSpace.getInstance(ColorSpace.CS_sRGB));
        }

        // Orig Time no int_pack = 51792
        // Simple int_pack       = 19600
        boolean use_int_pack = (is_INT_PACK(srcCM) && is_INT_PACK(dstCM));
        // use_int_pack = false;

        switch (rule.getRule()) {
        case CompositeRule.RULE_OVER:
            if (!dstCM.hasAlpha()) {
                if (use_int_pack)
                    return new OverCompositeContext_INT_PACK_NA(srcCM, dstCM);
                else
                    return new OverCompositeContext_NA  (srcCM, dstCM);
            }

            if (!use_int_pack)
                return new OverCompositeContext(srcCM, dstCM);

            if (srcCM.isAlphaPremultiplied())
                return new OverCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new OverCompositeContext_INT_PACK_UNPRE(srcCM, dstCM);

        case CompositeRule.RULE_IN:
            if (use_int_pack)
                return new InCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new InCompositeContext  (srcCM, dstCM);

        case CompositeRule.RULE_OUT:
            if (use_int_pack)
                return new OutCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new OutCompositeContext (srcCM, dstCM);

        case CompositeRule.RULE_ATOP:
            if (use_int_pack)
                return new AtopCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new AtopCompositeContext(srcCM, dstCM);

        case CompositeRule.RULE_XOR:
            if (use_int_pack)
                return new XorCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new XorCompositeContext (srcCM, dstCM);

        case CompositeRule.RULE_ARITHMETIC:
            float [] coeff = rule.getCoefficients();
            if (use_int_pack)
                return new ArithCompositeContext_INT_PACK_LUT
                    (srcCM, dstCM, coeff[0], coeff[1], coeff[2], coeff[3]);
            else
                return new ArithCompositeContext
                    (srcCM, dstCM, coeff[0], coeff[1], coeff[2], coeff[3]);

        case CompositeRule.RULE_MULTIPLY:
            if (use_int_pack)
                return new MultiplyCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new MultiplyCompositeContext(srcCM, dstCM);

        case CompositeRule.RULE_SCREEN:
            if (use_int_pack)
                return new ScreenCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new ScreenCompositeContext  (srcCM, dstCM);

        case CompositeRule.RULE_DARKEN:
            if (use_int_pack)
                return new DarkenCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new DarkenCompositeContext  (srcCM, dstCM);

        case CompositeRule.RULE_LIGHTEN:
            if (use_int_pack)
                return new LightenCompositeContext_INT_PACK(srcCM, dstCM);
            else
                return new LightenCompositeContext (srcCM, dstCM);

        default:
            throw new UnsupportedOperationException
                ("Unknown composite rule requested.");
        }

    }

    public abstract static class AlphaPreCompositeContext
        implements CompositeContext {

        ColorModel srcCM, dstCM;
        AlphaPreCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            this.srcCM = srcCM;
            this.dstCM = dstCM;
        }

        public void dispose() {
            srcCM = null;
            dstCM = null;
        }

        protected abstract void precompose(Raster src, Raster dstIn,
                                           WritableRaster dstOut);

        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
            ColorModel srcPreCM = srcCM;
            if (!srcCM.isAlphaPremultiplied())
                srcPreCM = GraphicsUtil.coerceData((WritableRaster)src,
                                                   srcCM, true);

            ColorModel dstPreCM = dstCM;
            if (!dstCM.isAlphaPremultiplied())
                dstPreCM = GraphicsUtil.coerceData((WritableRaster)dstIn,
                                                   dstCM, true);

            precompose(src, dstIn, dstOut);

            if (!srcCM.isAlphaPremultiplied())
                GraphicsUtil.coerceData((WritableRaster)src,
                                        srcPreCM, false);

            if (!dstCM.isAlphaPremultiplied()) {
                GraphicsUtil.coerceData(dstOut, dstPreCM, false);

                if (dstIn != dstOut)
                    GraphicsUtil.coerceData((WritableRaster)dstIn,
                                            dstPreCM, false);
            }
        }
    }

    public abstract static class AlphaPreCompositeContext_INT_PACK
        extends AlphaPreCompositeContext {

        AlphaPreCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        protected abstract void precompose_INT_PACK
            (final int width, final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp);

        protected void precompose(Raster src, Raster dstIn,
                                           WritableRaster dstOut) {

            int x0=dstOut.getMinX();
            int w =dstOut.getWidth();

            int y0=dstOut.getMinY();
            int h =dstOut.getHeight();

            SinglePixelPackedSampleModel srcSPPSM;
            srcSPPSM = (SinglePixelPackedSampleModel)src.getSampleModel();

            final int     srcScanStride = srcSPPSM.getScanlineStride();
            DataBufferInt srcDB         = (DataBufferInt)src.getDataBuffer();
            final int []  srcPixels     = srcDB.getBankData()[0];
            final int     srcBase =
                (srcDB.getOffset() +
                 srcSPPSM.getOffset(x0-src.getSampleModelTranslateX(),
                                    y0-src.getSampleModelTranslateY()));


            SinglePixelPackedSampleModel dstInSPPSM;
            dstInSPPSM = (SinglePixelPackedSampleModel)dstIn.getSampleModel();

            final int dstInScanStride = dstInSPPSM.getScanlineStride();
            DataBufferInt dstInDB     = (DataBufferInt)dstIn.getDataBuffer();
            final int []  dstInPixels     = dstInDB.getBankData()[0];
            final int     dstInBase =
                (dstInDB.getOffset() +
                 dstInSPPSM.getOffset(x0-dstIn.getSampleModelTranslateX(),
                                      y0-dstIn.getSampleModelTranslateY()));

            SinglePixelPackedSampleModel dstOutSPPSM
                = (SinglePixelPackedSampleModel)dstOut.getSampleModel();

            final int dstOutScanStride = dstOutSPPSM.getScanlineStride();
            DataBufferInt dstOutDB     = (DataBufferInt)dstOut.getDataBuffer();
            final int []  dstOutPixels     = dstOutDB.getBankData()[0];
            final int     dstOutBase =
                (dstOutDB.getOffset() +
                 dstOutSPPSM.getOffset(x0-dstOut.getSampleModelTranslateX(),
                                       y0-dstOut.getSampleModelTranslateY()));

            final int   srcAdjust  =    srcScanStride - w;
            final int  dstInAdjust =  dstInScanStride - w;
            final int dstOutAdjust = dstOutScanStride - w;

            precompose_INT_PACK(w, h,
                                srcPixels,    srcAdjust,    srcBase,
                                dstInPixels,  dstInAdjust,  dstInBase,
                                dstOutPixels, dstOutAdjust, dstOutBase);
        }
    }


    /**
     * This implements SRC_OVER for 4 band byte data.
     */
    public static class OverCompositeContext
        extends AlphaPreCompositeContext {
        OverCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {
                    final int dstM = (255-srcPix[sp+3])*norm;
                    dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
                    ++sp;
                    dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
                    ++sp;
                    dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
                    ++sp;
                    dstPix[sp] = srcPix[sp] + ((dstPix[sp]*dstM +pt5)>>>24);
                    ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }

        }
    }

    /**
     * This implements SRC_OVER for 4 band byte src data and
     * 3 band byte dst data.
     */
    public static class OverCompositeContext_NA
        extends AlphaPreCompositeContext {
        OverCompositeContext_NA(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int srcSP  = 0;
                int dstSP  = 0;
                int end = w*4;
                while (srcSP<end) {
                    final int dstM = (255-srcPix[srcSP+3])*norm;
                    dstPix[dstSP] =
                        srcPix[srcSP] + ((dstPix[dstSP]*dstM +pt5)>>>24);
                    ++srcSP; ++dstSP;
                    dstPix[dstSP] =
                        srcPix[srcSP] + ((dstPix[dstSP]*dstM +pt5)>>>24);
                    ++srcSP; ++dstSP;
                    dstPix[dstSP] =
                        srcPix[srcSP] + ((dstPix[dstSP]*dstM +pt5)>>>24);
                    srcSP+=2; ++dstSP;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }
        }
    }

    /**
     * This implements SRC_OVER for Int packed data where the src is
     * premultiplied.
     */
    public static class OverCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        OverCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, dstInP, dstM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP   = srcPixels  [srcSp++];
                    dstInP = dstInPixels[dstInSp++];

                    dstM = (255-(srcP>>>24))*norm;
                    dstOutPixels[dstOutSp++] =
                        (((     srcP & 0xFF000000) +
                          (((((dstInP>>>24)     )*dstM+pt5)&0xFF000000)     ))|
                         ((     srcP & 0x00FF0000) +
                          (((((dstInP>> 16)&0xFF)*dstM+pt5)&0xFF000000)>>> 8))|
                         ((     srcP & 0x0000FF00) +
                          (((((dstInP>>  8)&0xFF)*dstM+pt5)&0xFF000000)>>>16))|
                         ((     srcP & 0x000000FF) +
                          (((((dstInP     )&0xFF)*dstM+pt5)         )>>>24)));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    /**
     * This implements SRC_OVER for Int packed data and dest has no Alpha...
     */
    public static class OverCompositeContext_INT_PACK_NA
        extends AlphaPreCompositeContext_INT_PACK {
        OverCompositeContext_INT_PACK_NA(ColorModel srcCM, ColorModel dstCM) {
            super (srcCM, dstCM);
        }

        // When we get here src data has been premultiplied.
        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, dstInP, dstM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP   = srcPixels  [srcSp++];
                    dstInP = dstInPixels[dstInSp++];

                    dstM = (255-(srcP>>>24))*norm;
                    dstOutPixels[dstOutSp++] =
                        (((     srcP & 0x00FF0000) +
                          (((((dstInP>> 16)&0xFF)*dstM+pt5)&0xFF000000)>>> 8))|
                         ((     srcP & 0x0000FF00) +
                          (((((dstInP>>  8)&0xFF)*dstM+pt5)&0xFF000000)>>>16))|
                         ((     srcP & 0x000000FF) +
                          (((((dstInP     )&0xFF)*dstM+pt5)         )>>>24)));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    /**
     * This implements SRC_OVER for Int packed data where the src is
     * unpremultiplied.  This avoids having to multiply the alpha on the
     * the source then divide it out again.
     */
    public static class OverCompositeContext_INT_PACK_UNPRE
        extends AlphaPreCompositeContext_INT_PACK {
        OverCompositeContext_INT_PACK_UNPRE
            (ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);

            if (srcCM.isAlphaPremultiplied())
                throw new IllegalArgumentException
                    ("OverCompositeContext_INT_PACK_UNPRE is only for" +
                     "sources with unpremultiplied alpha");
        }

        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
            ColorModel dstPreCM = dstCM;
            if (!dstCM.isAlphaPremultiplied())
                dstPreCM = GraphicsUtil.coerceData((WritableRaster)dstIn,
                                                   dstCM, true);

            precompose(src, dstIn, dstOut);

            if (!dstCM.isAlphaPremultiplied()) {
                GraphicsUtil.coerceData(dstOut, dstPreCM, false);

                if (dstIn != dstOut)
                    GraphicsUtil.coerceData((WritableRaster)dstIn,
                                            dstPreCM, false);
            }
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM, dstP, dstM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP   = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcM = (    (srcP>>>24))*norm;
                    dstM = (255-(srcP>>>24))*norm;

                    dstOutPixels[dstOutSp++] =
                        ((((( srcP&0xFF000000)      +
                            ((dstP>>>24)     )*dstM + pt5)&0xFF000000)     ) |
                         (((((srcP>> 16)&0xFF)*srcM +
                            ((dstP>> 16)&0xFF)*dstM + pt5)&0xFF000000)>>> 8) |
                         (((((srcP>>  8)&0xFF)*srcM +
                            ((dstP>>  8)&0xFF)*dstM + pt5)&0xFF000000)>>>16) |
                         (((((srcP     )&0xFF)*srcM +
                            ((dstP     )&0xFF)*dstM + pt5)           )>>>24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class InCompositeContext
        extends AlphaPreCompositeContext {
        InCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {
                    final int srcM = dstPix[sp+3]*norm;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }

        }
    }

    public static class InCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        InCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcM = (dstInPixels[dstInSp++]>>>24)*norm;
                    srcP = srcPixels   [srcSp++];
                    dstOutPixels[dstOutSp++] =
                        ((((((srcP>>>24)     )*srcM + pt5)&0xFF000000)     ) |
                         (((((srcP>> 16)&0xFF)*srcM + pt5)&0xFF000000)>>> 8) |
                         (((((srcP>>  8)&0xFF)*srcM + pt5)&0xFF000000)>>>16) |
                         (((((srcP     )&0xFF)*srcM + pt5)           )>>>24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class OutCompositeContext
        extends AlphaPreCompositeContext {
        OutCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {
                    final int srcM = (255-dstPix[sp+3])*norm;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM + pt5)>>>24; ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }

        }
    }

    public static class OutCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        OutCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcM = (255-(dstInPixels[dstInSp++]>>>24))*norm;
                    srcP = srcPixels   [srcSp++];
                    dstOutPixels[dstOutSp++] =
                        ((((((srcP>>>24)     )*srcM + pt5)&0xFF000000)     ) |
                         (((((srcP>> 16)&0xFF)*srcM + pt5)&0xFF000000)>>> 8) |
                         (((((srcP>>  8)&0xFF)*srcM + pt5)&0xFF000000)>>>16) |
                         (((((srcP     )&0xFF)*srcM + pt5)           )>>>24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class AtopCompositeContext
        extends AlphaPreCompositeContext {
        AtopCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {
                    final int srcM = (    dstPix[sp+3])*norm;
                    final int dstM = (255-srcPix[sp+3])*norm;
                    dstPix[sp] =(srcPix[sp]*srcM + dstPix[sp]*dstM +pt5)>>>24;
                    ++sp;
                    dstPix[sp] =(srcPix[sp]*srcM + dstPix[sp]*dstM +pt5)>>>24;
                    ++sp;
                    dstPix[sp] =(srcPix[sp]*srcM + dstPix[sp]*dstM +pt5)>>>24;
                    sp+=2;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }

        }
    }

    public static class AtopCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        AtopCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM, dstP, dstM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcM = (     dstP>>>24) *norm;
                    dstM = (255-(srcP>>>24))*norm;

                    dstOutPixels[dstOutSp++] =
                        ((dstP&0xFF000000)                                   |
                         (((((srcP>> 16)&0xFF)*srcM +
                            ((dstP>> 16)&0xFF)*dstM + pt5)&0xFF000000)>>> 8) |
                         (((((srcP>>  8)&0xFF)*srcM +
                            ((dstP>>  8)&0xFF)*dstM + pt5)&0xFF000000)>>>16) |
                         (((((srcP     )&0xFF)*srcM +
                            ((dstP     )&0xFF)*dstM + pt5)           )>>>24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class XorCompositeContext
        extends AlphaPreCompositeContext {

        XorCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {
                    final int srcM = (255-dstPix[sp+3])*norm;
                    final int dstM = (255-srcPix[sp+3])*norm;

                    dstPix[sp] = (srcPix[sp]*srcM +
                                  dstPix[sp]*dstM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM +
                                  dstPix[sp]*dstM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM +
                                  dstPix[sp]*dstM + pt5)>>>24; ++sp;
                    dstPix[sp] = (srcPix[sp]*srcM +
                                  dstPix[sp]*dstM + pt5)>>>24; ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }

        }
    }

    public static class XorCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        XorCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM, dstP, dstM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcM = (255-(dstP>>>24))*norm;
                    dstM = (255-(srcP>>>24))*norm;

                    dstOutPixels[dstOutSp++] =
                        ((((((srcP>>>24)     )*srcM +
                            ((dstP>>>24)     )*dstM + pt5)&0xFF000000)     ) |
                         (((((srcP>> 16)&0xFF)*srcM +
                            ((dstP>> 16)&0xFF)*dstM + pt5)&0xFF000000)>>> 8) |
                         (((((srcP>>  8)&0xFF)*srcM +
                            ((dstP>>  8)&0xFF)*dstM + pt5)&0xFF000000)>>>16) |
                         (((((srcP     )&0xFF)*srcM +
                            ((dstP     )&0xFF)*dstM + pt5)           )>>>24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class ArithCompositeContext
        extends AlphaPreCompositeContext {
        float k1, k2, k3, k4;
        ArithCompositeContext(ColorModel srcCM,
                              ColorModel dstCM,
                              float k1, float k2, float k3, float k4) {
            super(srcCM, dstCM);
            this.k1 = k1;
            this.k2 = k2;
            this.k3 = k3;
            this.k4 = k4;
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();
            int bands = dstOut.getNumBands();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            float kk1 = k1/255.0f;
            float kk4 = k4*255.0f+0.5f;

            int y, i, b, val, max;
            for (y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                for (i=0; i<srcPix.length; i++) {
                    max=0;
                    for (b=1; b<bands; b++, i++) {
                        val =(int)((kk1*srcPix[i]*dstPix[i]) +
                                   k2*srcPix[i] + k3*dstPix[i] + kk4);
                        if ((val & 0xFFFFFF00) != 0)
                            if ((val & 0x80000000) != 0) val = 0;
                            else                         val = 255;
                        if (val > max) max=val;
                        dstPix[i] = val;
                    }

                    val =(int)((kk1*srcPix[i]*dstPix[i]) +
                               k2*srcPix[i] + k3*dstPix[i] + kk4);
                    if ((val & 0xFFFFFF00) != 0)
                        if ((val & 0x80000000) != 0) val = 0;
                        else                         val = 255;
                    if (val > max)
                        dstPix[i] = val;
                    else
                        dstPix[i] = max;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }
        }
    }

    public static class ArithCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        float k1, k2, k3, k4;
        ArithCompositeContext_INT_PACK(ColorModel srcCM,
                                       ColorModel dstCM,
                                       float k1, float k2,
                                       float k3, float k4) {
            super(srcCM, dstCM);
            this.k1 = k1/255.0f;
            this.k2 = k2;
            this.k3 = k3;
            this.k4 = k4*255.0f+0.5f;
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            int srcP, dstP, a, r, g, b;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels   [srcSp++];
                    dstP = dstInPixels   [dstInSp++];
                    a = (int)((srcP>>>24)*(dstP>>>24)*k1 +
                              (srcP>>>24)*k2 + (dstP>>>24)*k3 + k4);
                    if ((a & 0xFFFFFF00) != 0)
                        if ((a & 0x80000000) != 0) a = 0;
                        else                       a = 255;

                    r = (int)(((srcP>> 16)&0xFF)*((dstP>> 16)&0xFF)*k1 +
                              ((srcP>> 16)&0xFF)*k2 +
                              ((dstP>> 16)&0xFF)*k3 + k4);
                    if ((r & 0xFFFFFF00) != 0)
                        if ((r & 0x80000000) != 0) r = 0;
                        else                       r = 255;
                    if (a < r) a = r;

                    g = (int)(((srcP>>  8)&0xFF)*((dstP>>  8)&0xFF)*k1 +
                              ((srcP>>  8)&0xFF)*k2 +
                              ((dstP>>  8)&0xFF)*k3 + k4);
                    if ((g & 0xFFFFFF00) != 0)
                        if ((g & 0x80000000) != 0) g = 0;
                        else                       g = 255;
                    if (a < g) a = g;

                    b = (int)((srcP&0xFF)*(dstP&0xFF)*k1 +
                              (srcP&0xFF)*k2 + (dstP&0xFF)*k3 + k4);
                    if ((b & 0xFFFFFF00) != 0)
                        if ((b & 0x80000000) != 0) b = 0;
                        else                       b = 255;
                    if (a < b) a = b;

                    dstOutPixels[dstOutSp++]
                        = ((a<<24) | (r<<16) | (g<<8) | b);
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
            // long endTime = System.currentTimeMillis();
            // System.out.println("Arith Time: " + (endTime-startTime));
        }
    }

    public static class ArithCompositeContext_INT_PACK_LUT
        extends AlphaPreCompositeContext_INT_PACK {
        byte [] lut;
        ArithCompositeContext_INT_PACK_LUT(ColorModel srcCM,
                                           ColorModel dstCM,
                                           float k1, float k2,
                                           float k3, float k4) {
            super(srcCM, dstCM);
            k1 = k1/255.0f;
            k4 = k4*255.0f+0.5f;
            int sz = 256*256;
            lut = new byte[sz];
            int val;
            for (int i=0; i<sz; i++) {
                val = (int)((i>>8)*(i&0xFF)*k1 + (i>>8)*k2 + (i&0xFF)*k3 + k4);
                if ((val & 0xFFFFFF00) != 0)
                    if ((val & 0x80000000) != 0) val = 0;
                    else                         val = 255;
                lut[i] = (byte)val;
            }
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            byte[] workTbl = lut;   // local is cheaper
            int srcP, dstP;
            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    int a = 0xFF & workTbl[(((srcP>> 16)&0xFF00)|((dstP>>>24)       ))];
                    int r = 0xFF & workTbl[(((srcP>>  8)&0xFF00)|((dstP>> 16)&0x00FF))];
                    int g = 0xFF & workTbl[(((srcP     )&0xFF00)|((dstP>>  8)&0x00FF))];
                    int b = 0xFF & workTbl[(((srcP<<  8)&0xFF00)|((dstP     )&0x00FF))];
                    if (r>a) a = r;
                    if (g>a) a = g;
                    if (b>a) a = b;
                    dstOutPixels[dstOutSp++] = (a<<24)|(r<<16)|(g<<8)|(b);
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
            // long endTime = System.currentTimeMillis();
            // System.out.println("ArithLut Time: " + (endTime-startTime));
        }
    }


    /**
     * The following classes implement the various blend modes from SVG.  */
    public static class MultiplyCompositeContext
        extends AlphaPreCompositeContext {

        MultiplyCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);
            int srcM, dstM;

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {
                    srcM = 255-dstPix[sp+3];
                    dstM = 255-srcPix[sp+3];

                    dstPix[sp] = ((srcPix[sp]*srcM + dstPix[sp]*dstM +
                                   srcPix[sp]*dstPix[sp])*norm + pt5)>>>24;
                    ++sp;

                    dstPix[sp] = ((srcPix[sp]*srcM + dstPix[sp]*dstM +
                                   srcPix[sp]*dstPix[sp])*norm + pt5)>>>24;
                    ++sp;

                    dstPix[sp] = ((srcPix[sp]*srcM + dstPix[sp]*dstM +
                                   srcPix[sp]*dstPix[sp])*norm + pt5)>>>24;
                    ++sp;

                    dstPix[sp] = (srcPix[sp] + dstPix[sp] -
                                  ((dstPix[sp]*srcPix[sp]*norm + pt5)>>>24));
                    ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }
        }
    }

    public static class MultiplyCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        MultiplyCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcA, srcR, srcG, srcB, srcM;
            int dstP, dstA, dstR, dstG, dstB, dstM;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcA = (srcP>>>24);
                    dstA = (dstP>>>24);
                    srcR = (srcP>> 16)&0xFF;
                    dstR = (dstP>> 16)&0xFF;
                    srcG = (srcP>>  8)&0xFF;
                    dstG = (dstP>>  8)&0xFF;
                    srcB = (srcP     )&0xFF;
                    dstB = (dstP     )&0xFF;

                    srcM = 255-dstA;
                    dstM = 255-srcA;

                    dstOutPixels[dstOutSp++] =
                        (((((srcR*srcM + dstR*dstM + srcR*dstR)
                            *norm + pt5)&0xFF000000)>>> 8) |
                         ((((srcG*srcM + dstG*dstM + srcG*dstG)
                            *norm + pt5)&0xFF000000)>>>16) |
                         ((((srcB*srcM + dstB*dstM + srcB*dstB)
                            *norm + pt5)           )>>>24) |
                         ((srcA + dstA - ((srcA*dstA*norm + pt5)>>>24))<<24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class ScreenCompositeContext
        extends AlphaPreCompositeContext {

        ScreenCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                int sp  = 0;
                int end = w*4;
                while(sp<end) {

                    int iSrcPix = srcPix[ sp ];
                    int iDstPix = dstPix[ sp ];

                    dstPix[sp] = ( iSrcPix + iDstPix -
                                  ((iDstPix*iSrcPix*norm + pt5)>>>24));
                    ++sp;
                    iSrcPix = srcPix[ sp ];
                    iDstPix = dstPix[ sp ];
                    dstPix[sp] = ( iSrcPix + iDstPix -
                                  ((iDstPix*iSrcPix*norm + pt5)>>>24));
                    ++sp;
                    iSrcPix = srcPix[ sp ];
                    iDstPix = dstPix[ sp ];
                    dstPix[sp] = ( iSrcPix + iDstPix -
                                  ((iDstPix*iSrcPix*norm + pt5)>>>24));
                    ++sp;
                    iSrcPix = srcPix[ sp ];
                    iDstPix = dstPix[ sp ];
                    dstPix[sp] = ( iSrcPix + iDstPix -
                                  ((iDstPix*iSrcPix*norm + pt5)>>>24));
                    ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }
        }
    }

    public static class ScreenCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        ScreenCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcA, srcR, srcG, srcB;
            int dstP, dstA, dstR, dstG, dstB;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcA = (srcP>>>24);
                    dstA = (dstP>>>24);
                    srcR = (srcP>> 16)&0xFF;
                    dstR = (dstP>> 16)&0xFF;
                    srcG = (srcP>>  8)&0xFF;
                    dstG = (dstP>>  8)&0xFF;
                    srcB = (srcP     )&0xFF;
                    dstB = (dstP     )&0xFF;

                    dstOutPixels[dstOutSp++] =
                        (((srcR + dstR - ((srcR*dstR*norm + pt5)>>>24))<<16)|
                         ((srcG + dstG - ((srcG*dstG*norm + pt5)>>>24))<< 8)|
                         ((srcB + dstB - ((srcB*dstB*norm + pt5)>>>24))    )|
                         ((srcA + dstA - ((srcA*dstA*norm + pt5)>>>24))<<24));
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class DarkenCompositeContext
        extends AlphaPreCompositeContext {

        DarkenCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int sp, srcM, dstM, t1, t2;

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                sp  = 0;
                final int end = w*4;
                while(sp<end) {
                    srcM = 255-dstPix[sp+3];
                    dstM = 255-srcPix[sp+3];

                    t1 = ((srcM*srcPix[sp]*norm + pt5)>>>24) + dstPix[sp];
                    t2 = ((dstM*dstPix[sp]*norm + pt5)>>>24) + srcPix[sp];
                    if (t1 > t2) dstPix[sp] = t2;
                    else         dstPix[sp] = t1;
                    ++sp;

                    t1 = ((srcM*srcPix[sp]*norm + pt5)>>>24) + dstPix[sp];
                    t2 = ((dstM*dstPix[sp]*norm + pt5)>>>24) + srcPix[sp];
                    if (t1 > t2) dstPix[sp] = t2;
                    else         dstPix[sp] = t1;
                    ++sp;

                    t1 = ((srcM*srcPix[sp]*norm + pt5)>>>24) + dstPix[sp];
                    t2 = ((dstM*dstPix[sp]*norm + pt5)>>>24) + srcPix[sp];
                    if (t1 > t2) dstPix[sp] = t2;
                    else         dstPix[sp] = t1;
                    ++sp;

                    dstPix[sp] = (srcPix[sp] + dstPix[sp] -
                                  ((dstPix[sp]*srcPix[sp]*norm + pt5)>>>24));
                    ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }
        }
    }

    public static class DarkenCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        DarkenCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM;
            int dstP, dstM, dstA, dstR, dstG, dstB;

            int srcV, dstV, tmp;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcV = (srcP>>>24);
                    dstV = (dstP>>>24);
                    srcM = (255-dstV)*norm;
                    dstM = (255-srcV)*norm;
                    dstA = (srcV + dstV - ((srcV*dstV*norm + pt5)>>>24));

                    srcV = (srcP>> 16)&0xFF;
                    dstV = (dstP>> 16)&0xFF;
                    dstR = ((srcM*srcV + pt5)>>>24) + dstV;
                    tmp  = ((dstM*dstV + pt5)>>>24) + srcV;
                    if (dstR > tmp) dstR = tmp;

                    srcV = (srcP>>  8)&0xFF;
                    dstV = (dstP>>  8)&0xFF;
                    dstG = ((srcM*srcV + pt5)>>>24) + dstV;
                    tmp  = ((dstM*dstV + pt5)>>>24) + srcV;
                    if (dstG > tmp) dstG = tmp;


                    srcV = (srcP     )&0xFF;
                    dstV = (dstP     )&0xFF;
                    dstB = ((srcM*srcV + pt5)>>>24) + dstV;
                    tmp  = ((dstM*dstV + pt5)>>>24) + srcV;
                    if (dstB > tmp) dstB = tmp;

                    dstA &= 0xFF; // trim to 8 bit
                    dstR &= 0xFF;
                    dstG &= 0xFF;
                    dstB &= 0xFF;

                    dstOutPixels[dstOutSp++] =
                        ((dstA<<24) | (dstR<<16) | (dstG<< 8) | dstB);
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

    public static class LightenCompositeContext
        extends AlphaPreCompositeContext {

        LightenCompositeContext(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose(Raster src, Raster dstIn,
                               WritableRaster dstOut) {
            int [] srcPix = null;
            int [] dstPix = null;

            int x=dstOut.getMinX();
            int w=dstOut.getWidth();

            int y0=dstOut.getMinY();
            int y1=y0 + dstOut.getHeight();

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int sp, srcM, dstM, t1, t2;

            for (int y = y0; y<y1; y++) {
                srcPix = src.getPixels  (x, y, w, 1, srcPix);
                dstPix = dstIn.getPixels(x, y, w, 1, dstPix);
                sp  = 0;
                final int end = w*4;
                while(sp<end) {
                    srcM = 255-dstPix[sp+3];
                    dstM = 255-srcPix[sp+3];

                    t1 = ((srcM*srcPix[sp]*norm + pt5)>>>24) + dstPix[sp];
                    t2 = ((dstM*dstPix[sp]*norm + pt5)>>>24) + srcPix[sp];
                    if (t1 > t2) dstPix[sp] = t1;
                    else         dstPix[sp] = t2;
                    ++sp;

                    t1 = ((srcM*srcPix[sp]*norm + pt5)>>>24) + dstPix[sp];
                    t2 = ((dstM*dstPix[sp]*norm + pt5)>>>24) + srcPix[sp];
                    if (t1 > t2) dstPix[sp] = t1;
                    else         dstPix[sp] = t2;
                    ++sp;

                    t1 = ((srcM*srcPix[sp]*norm + pt5)>>>24) + dstPix[sp];
                    t2 = ((dstM*dstPix[sp]*norm + pt5)>>>24) + srcPix[sp];
                    if (t1 > t2) dstPix[sp] = t1;
                    else         dstPix[sp] = t2;
                    ++sp;

                    dstPix[sp] = (srcPix[sp] + dstPix[sp] -
                                  ((dstPix[sp]*srcPix[sp]*norm + pt5)>>>24));
                    ++sp;
                }
                dstOut.setPixels(x, y, w, 1, dstPix);
            }
        }
    }

    public static class LightenCompositeContext_INT_PACK
        extends AlphaPreCompositeContext_INT_PACK {
        LightenCompositeContext_INT_PACK(ColorModel srcCM, ColorModel dstCM) {
            super(srcCM, dstCM);
        }

        public void precompose_INT_PACK
            (final int width,           final int height,
             final int [] srcPixels,    final int srcAdjust,    int srcSp,
             final int [] dstInPixels,  final int dstInAdjust,  int dstInSp,
             final int [] dstOutPixels, final int dstOutAdjust, int dstOutSp) {

            final int norm = (1<<24)/255;
            final int pt5  = (1<<23);

            int srcP, srcM;
            int dstP, dstM, dstA, dstR, dstG, dstB;

            int srcV, dstV, tmp;

            for (int y = 0; y<height; y++) {
                final int end = dstOutSp+width;
                while (dstOutSp<end) {
                    srcP = srcPixels  [srcSp++];
                    dstP = dstInPixels[dstInSp++];

                    srcV = (srcP>>>24);
                    dstV = (dstP>>>24);
                    srcM = (255-dstV)*norm;
                    dstM = (255-srcV)*norm;
                    dstA = (srcV + dstV - ((srcV*dstV*norm + pt5)>>>24));

                    srcV = (srcP>> 16)&0xFF;
                    dstV = (dstP>> 16)&0xFF;
                    dstR = ((srcM*srcV + pt5)>>>24) + dstV;
                    tmp  = ((dstM*dstV + pt5)>>>24) + srcV;
                    if (dstR < tmp) dstR = tmp;

                    srcV = (srcP>>  8)&0xFF;
                    dstV = (dstP>>  8)&0xFF;
                    dstG = ((srcM*srcV + pt5)>>>24) + dstV;
                    tmp  = ((dstM*dstV + pt5)>>>24) + srcV;
                    if (dstG < tmp) dstG = tmp;


                    srcV = (srcP     )&0xFF;
                    dstV = (dstP     )&0xFF;
                    dstB = ((srcM*srcV + pt5)>>>24) + dstV;
                    tmp  = ((dstM*dstV + pt5)>>>24) + srcV;
                    if (dstB < tmp) dstB = tmp;

                    dstA &= 0xFF; // trim to 8 bit
                    dstR &= 0xFF;
                    dstG &= 0xFF;
                    dstB &= 0xFF;

                    dstOutPixels[dstOutSp++] =
                        ((dstA<<24) | (dstR<<16) | (dstG<< 8) | dstB);
                }
                srcSp    += srcAdjust;
                dstInSp  += dstInAdjust;
                dstOutSp += dstOutAdjust;
            }
        }
    }

}
