public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
      if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT
          || dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT
          || dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) {
        throw new IllegalStateException("Source and destination must store pixels as INT.");
      }

      int width = Math.min(src.getWidth(), dstIn.getWidth());
      int height = Math.min(src.getHeight(), dstIn.getHeight());

      float alpha = composite.getAlpha();

      int[] srcPixel = new int[4];
      int[] dstPixel = new int[4];
      int[] srcPixels = new int[width];
      int[] dstPixels = new int[width];

      for (int y = 0; y < height; y++) {
        src.getDataElements(0, y, width, 1, srcPixels);
        dstIn.getDataElements(0, y, width, 1, dstPixels);
        for (int x = 0; x < width; x++) {
          // pixels are stored as INT_ARGB
          // our arrays are [R, G, B, A]
          int pixel = srcPixels[x];
          srcPixel[0] = (pixel >> 16) & 0xFF;
          srcPixel[1] = (pixel >> 8) & 0xFF;
          srcPixel[2] = (pixel) & 0xFF;
          srcPixel[3] = (pixel >> 24) & 0xFF;

          pixel = dstPixels[x];
          dstPixel[0] = (pixel >> 16) & 0xFF;
          dstPixel[1] = (pixel >> 8) & 0xFF;
          dstPixel[2] = (pixel) & 0xFF;
          dstPixel[3] = (pixel >> 24) & 0xFF;

          int[] result = blender.blend(srcPixel, dstPixel);

          // mixes the result with the opacity
          dstPixels[x] =
              ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24
                  | ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16
                  | ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8
                  | (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF;
        }
        dstOut.setDataElements(0, y, width, 1, dstPixels);
      }
    }
    public static Blender getBlenderFor(BlendComposite composite) {
      switch (composite.getMode()) {
        case NORMAL:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return src;
            }
          };
        case ADD:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                Math.min(255, src[0] + dst[0]),
                Math.min(255, src[1] + dst[1]),
                Math.min(255, src[2] + dst[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case AVERAGE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                (src[0] + dst[0]) >> 1,
                (src[1] + dst[1]) >> 1,
                (src[2] + dst[2]) >> 1,
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case BLUE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {dst[0], src[1], dst[2], Math.min(255, src[3] + dst[3])};
            }
          };
        case COLOR:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              float[] srcHSL = new float[3];
              RGBtoHSL(src[0], src[1], src[2], srcHSL);
              float[] dstHSL = new float[3];
              RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);

              int[] result = new int[4];
              HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result);
              result[3] = Math.min(255, src[3] + dst[3]);

              return result;
            }
          };
        case COLOR_BURN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                src[0] == 0 ? 0 : Math.max(0, 255 - (((255 - dst[0]) << 8) / src[0])),
                src[1] == 0 ? 0 : Math.max(0, 255 - (((255 - dst[1]) << 8) / src[1])),
                src[2] == 0 ? 0 : Math.max(0, 255 - (((255 - dst[2]) << 8) / src[2])),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case COLOR_DODGE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                src[0] == 255 ? 255 : Math.min((dst[0] << 8) / (255 - src[0]), 255),
                src[1] == 255 ? 255 : Math.min((dst[1] << 8) / (255 - src[1]), 255),
                src[2] == 255 ? 255 : Math.min((dst[2] << 8) / (255 - src[2]), 255),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case DARKEN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                Math.min(src[0], dst[0]),
                Math.min(src[1], dst[1]),
                Math.min(src[2], dst[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case DIFFERENCE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                Math.abs(dst[0] - src[0]),
                Math.abs(dst[1] - src[1]),
                Math.abs(dst[2] - src[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case EXCLUSION:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] + src[0] - (dst[0] * src[0] >> 7),
                dst[1] + src[1] - (dst[1] * src[1] >> 7),
                dst[2] + src[2] - (dst[2] * src[2] >> 7),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case FREEZE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                src[0] == 0 ? 0 : Math.max(0, 255 - (255 - dst[0]) * (255 - dst[0]) / src[0]),
                src[1] == 0 ? 0 : Math.max(0, 255 - (255 - dst[1]) * (255 - dst[1]) / src[1]),
                src[2] == 0 ? 0 : Math.max(0, 255 - (255 - dst[2]) * (255 - dst[2]) / src[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case GLOW:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] == 255 ? 255 : Math.min(255, src[0] * src[0] / (255 - dst[0])),
                dst[1] == 255 ? 255 : Math.min(255, src[1] * src[1] / (255 - dst[1])),
                dst[2] == 255 ? 255 : Math.min(255, src[2] * src[2] / (255 - dst[2])),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case GREEN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {dst[0], dst[1], src[2], Math.min(255, src[3] + dst[3])};
            }
          };
        case HARD_LIGHT:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                src[0] < 128 ? dst[0] * src[0] >> 7 : 255 - ((255 - src[0]) * (255 - dst[0]) >> 7),
                src[1] < 128 ? dst[1] * src[1] >> 7 : 255 - ((255 - src[1]) * (255 - dst[1]) >> 7),
                src[2] < 128 ? dst[2] * src[2] >> 7 : 255 - ((255 - src[2]) * (255 - dst[2]) >> 7),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case HEAT:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]),
                dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]),
                dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case HUE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              float[] srcHSL = new float[3];
              RGBtoHSL(src[0], src[1], src[2], srcHSL);
              float[] dstHSL = new float[3];
              RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);

              int[] result = new int[4];
              HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result);
              result[3] = Math.min(255, src[3] + dst[3]);

              return result;
            }
          };
        case INVERSE_COLOR_BURN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] == 0 ? 0 : Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])),
                dst[1] == 0 ? 0 : Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])),
                dst[2] == 0 ? 0 : Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case INVERSE_COLOR_DODGE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] == 255 ? 255 : Math.min((src[0] << 8) / (255 - dst[0]), 255),
                dst[1] == 255 ? 255 : Math.min((src[1] << 8) / (255 - dst[1]), 255),
                dst[2] == 255 ? 255 : Math.min((src[2] << 8) / (255 - dst[2]), 255),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case LIGHTEN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                Math.max(src[0], dst[0]),
                Math.max(src[1], dst[1]),
                Math.max(src[2], dst[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case LUMINOSITY:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              float[] srcHSL = new float[3];
              RGBtoHSL(src[0], src[1], src[2], srcHSL);
              float[] dstHSL = new float[3];
              RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);

              int[] result = new int[4];
              HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result);
              result[3] = Math.min(255, src[3] + dst[3]);

              return result;
            }
          };
        case MULTIPLY:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                (src[0] * dst[0]) >> 8,
                (src[1] * dst[1]) >> 8,
                (src[2] * dst[2]) >> 8,
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case NEGATION:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                255 - Math.abs(255 - dst[0] - src[0]),
                255 - Math.abs(255 - dst[1] - src[1]),
                255 - Math.abs(255 - dst[2] - src[2]),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case OVERLAY:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] < 128 ? dst[0] * src[0] >> 7 : 255 - ((255 - dst[0]) * (255 - src[0]) >> 7),
                dst[1] < 128 ? dst[1] * src[1] >> 7 : 255 - ((255 - dst[1]) * (255 - src[1]) >> 7),
                dst[2] < 128 ? dst[2] * src[2] >> 7 : 255 - ((255 - dst[2]) * (255 - src[2]) >> 7),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case RED:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {src[0], dst[1], dst[2], Math.min(255, src[3] + dst[3])};
            }
          };
        case REFLECT:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])),
                src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])),
                src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case SATURATION:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              float[] srcHSL = new float[3];
              RGBtoHSL(src[0], src[1], src[2], srcHSL);
              float[] dstHSL = new float[3];
              RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);

              int[] result = new int[4];
              HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result);
              result[3] = Math.min(255, src[3] + dst[3]);

              return result;
            }
          };
        case SCREEN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                255 - ((255 - src[0]) * (255 - dst[0]) >> 8),
                255 - ((255 - src[1]) * (255 - dst[1]) >> 8),
                255 - ((255 - src[2]) * (255 - dst[2]) >> 8),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case SOFT_BURN:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] + src[0] < 256
                    ? (dst[0] == 255 ? 255 : Math.min(255, (src[0] << 7) / (255 - dst[0])))
                    : Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])),
                dst[1] + src[1] < 256
                    ? (dst[1] == 255 ? 255 : Math.min(255, (src[1] << 7) / (255 - dst[1])))
                    : Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])),
                dst[2] + src[2] < 256
                    ? (dst[2] == 255 ? 255 : Math.min(255, (src[2] << 7) / (255 - dst[2])))
                    : Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case SOFT_DODGE:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                dst[0] + src[0] < 256
                    ? (src[0] == 255 ? 255 : Math.min(255, (dst[0] << 7) / (255 - src[0])))
                    : Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])),
                dst[1] + src[1] < 256
                    ? (src[1] == 255 ? 255 : Math.min(255, (dst[1] << 7) / (255 - src[1])))
                    : Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])),
                dst[2] + src[2] < 256
                    ? (src[2] == 255 ? 255 : Math.min(255, (dst[2] << 7) / (255 - src[2])))
                    : Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case SOFT_LIGHT:
          break;
        case STAMP:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)),
                Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)),
                Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
        case SUBTRACT:
          return new Blender() {
            @Override
            public int[] blend(int[] src, int[] dst) {
              return new int[] {
                Math.max(0, src[0] + dst[0] - 256),
                Math.max(0, src[1] + dst[1] - 256),
                Math.max(0, src[2] + dst[2] - 256),
                Math.min(255, src[3] + dst[3])
              };
            }
          };
      }
      throw new IllegalArgumentException("Blender not implement for " + composite.getMode().name());
    }