docs: static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
          // NOTE: This is optimized code for most common cases.
          // We create a DataBuffer from the pixel array directly,
          // and creating a raster based on the DataBuffer and ColorModel.
          // Creating rasters this way is faster and more resource-friendly, as
          // cm.createCompatibleWritableRaster allocates an
          // "empty" DataBuffer with a storage array of w*h. This array is
          // later discarded, and replaced in the raster.setDataElements() call.
          // The "old" way is kept as a more compatible fall-back mode.
  
          DataBuffer buffer = null;
          WritableRaster raster = null;
  
          int bands;
          if (pPixels instanceof int[]) {
              int[] data = (int[]) pPixels;
              buffer = new DataBufferInt(data, data.length);
              bands = pColorModel.getNumComponents();
          }
          else if (pPixels instanceof short[]) {
              short[] data = (short[]) pPixels;
              buffer = new DataBufferUShort(data, data.length);
              bands = data.length / (pWidth * pHeight);
          }
          else if (pPixels instanceof byte[]) {
              byte[] data = (byte[]) pPixels;
              buffer = new DataBufferByte(data, data.length);
  
              // NOTE: This only holds for gray and indexed with one byte per pixel...
              if (pColorModel instanceof IndexColorModel) {
                  bands = 1;
              }
              else {
                  bands = data.length / (pWidth * pHeight);
              }
          }
          else {
              // Fallback mode, slower & requires more memory, but compatible
              bands = -1;
  
              // Create raster from color model, w and h
              raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
              raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions..
          }
  
          if (raster == null) {
              if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) {
                  raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT);
              }
              else if (pColorModel instanceof PackedColorModel) {
                  PackedColorModel pcm = (PackedColorModel) pColorModel;
                  raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
              }
              else {
                  // (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
                  int[] bandsOffsets = new int[bands];
                  for (int i = 0; i < bands;) {
                      bandsOffsets[i] = bands - (++i);
                  }
  
                  raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT);
              }
          }
  
          return raster;
      }
  
      private static boolean isIndexedPacked(IndexColorModel pColorModel) {
          return (pColorModel.getPixelSize() == 1 || pColorModel.getPixelSize() == 2 || pColorModel.getPixelSize() == 4);
      }
  
      /**
       * Workaround for bug: TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR and
       * TYPE_4BYTE_ABGR_PRE are all converted to TYPE_CUSTOM when using the
       * default createCompatibleWritableRaster from ComponentColorModel.
       *
       * @param pOriginal the orignal image
       * @param pModel the original color model
       * @param width the requested width of the raster
       * @param height the requested height of the raster
       *
       * @return a new WritableRaster
       */
      static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int width, int height) {
          if (pModel == null || equals(pOriginal.getColorModel(), pModel)) {
              int[] bOffs;
              switch (pOriginal.getType()) {
                  case BufferedImage.TYPE_3BYTE_BGR:
                      bOffs = new int[]{2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
                      return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                            width, height,
                                                            width * 3, 3,
                                                            bOffs, null);
                  case BufferedImage.TYPE_4BYTE_ABGR:
                  case BufferedImage.TYPE_4BYTE_ABGR_PRE:
                      bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
                      return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                            width, height,
                                                            width * 4, 4,
                                                            bOffs, null);
                  case BufferedImage.TYPE_CUSTOM:
                      // Peek into the sample model to see if we have a sample model that will be incompatible with the default case
                      SampleModel sm = pOriginal.getRaster().getSampleModel();
                      if (sm instanceof ComponentSampleModel) {
                          bOffs = ((ComponentSampleModel) sm).getBandOffsets();
                          return Raster.createInterleavedRaster(sm.getDataType(),
                                                                width, height,
                                                                width * bOffs.length, bOffs.length,
                                                                bOffs, null);
                      }
                      // Else fall through
                  default:
                      return pOriginal.getColorModel().createCompatibleWritableRaster(width, height);
              }
          }
  
          return pModel.createCompatibleWritableRaster(width, height);
      }
  
      /**
       * Converts the {@code Image} to a {@code BufferedImage} of the given type.
       * The new image will have the same {@code ColorModel}, {@code Raster} and
       * properties as the original image, if possible.
       * <p/>
       * If the image is already a {@code BufferedImage} of the given type, it
       * is simply returned and no conversion takes place.
       *
       * @param pOriginal the image to convert.
       * @param pType the type of buffered image
       *
       * @return a {@code BufferedImage}
       *
       * @throws IllegalArgumentException if {@code pOriginal == null}
       * or if {@code pType} is not a valid type for {@code BufferedImage}
       *
       * @see java.awt.image.BufferedImage#getType()
       */
      public static BufferedImage toBuffered(Image pOriginal, int pType) {
          return toBuffered(pOriginal, pType, null);
      }
  
      /**
       *
       * @param pOriginal the original image
       * @param pType the type of {@code BufferedImage} to create
       * @param pICM the optional {@code IndexColorModel} to use. If not
       * {@code null} the {@code pType} must be compatible with the color model
       * @return a {@code BufferedImage}
       * @throws IllegalArgumentException if {@code pType} is not compatible with
       * the color model
       */
      private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) {
          // Don't convert if it already is BufferedImage and correct type
          if ((pOriginal instanceof BufferedImage)
                  && ((BufferedImage) pOriginal).getType() == pType
                  && (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) {
              return (BufferedImage) pOriginal;
          }
          if (pOriginal == null) {
              throw new IllegalArgumentException("original == null");
          }
  
          //System.out.println("--> Doing full BufferedImage conversion, using Graphics.drawImage().");
  
          // Create a buffered image
          // NOTE: The getWidth and getHeight methods, will wait for the image
          BufferedImage image;
          if (pICM == null) {
              image = createBuffered(getWidth(pOriginal), getHeight(pOriginal), pType, Transparency.TRANSLUCENT);//new BufferedImage(getWidth(pOriginal), getHeight(pOriginal), pType);
          }
          else {
              image = new BufferedImage(getWidth(pOriginal), getHeight(pOriginal), pType, pICM);
          }
  
          // Draw the image onto the buffer
          drawOnto(image, pOriginal);
  
          return image;
      }
  
      /**
       * Draws the source image onto the buffered image, using
       * {@code AlphaComposite.Src} and coordinates {@code 0, 0}.
       *
       * @param pDestination the image to draw on
       * @param pSource the source image to draw
       *
       * @throws NullPointerException if {@code pDestination} or {@code pSource} is {@code null}
       */
      static void drawOnto(final BufferedImage pDestination, final Image pSource) {
          Graphics2D g = pDestination.createGraphics();
          try {
              g.setComposite(AlphaComposite.Src);
              g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
              g.drawImage(pSource, 0, 0, null);
          }
          finally {
              g.dispose();
          }
      }
  
      /**
       * Creates a flipped version of the given image.
       *
       * @param pImage the image to flip
       * @param pAxis the axis to flip around
       * @return a new {@code BufferedImage}
       */
      public static BufferedImage createFlipped(final Image pImage, final int pAxis) {
          switch (pAxis) {
              case FLIP_HORIZONTAL:
              case FLIP_VERTICAL:
              // TODO case FLIP_BOTH:?? same as rotate 180?
                  break;
              default:
                  throw new IllegalArgumentException("Illegal direction: " + pAxis);
          }
          BufferedImage source = toBuffered(pImage);
          AffineTransform transform;
          if (pAxis == FLIP_HORIZONTAL) {
              transform = AffineTransform.getTranslateInstance(0, source.getHeight());
              transform.scale(1, -1);
          }
          else {
              transform = AffineTransform.getTranslateInstance(source.getWidth(), 0);
              transform.scale(-1, 1);
          }
          AffineTransformOp transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
          return transformOp.filter(source, null);
      }
  
  
      /**
       * Rotates the image 90 degrees, clockwise (aka "rotate right"),
       * counter-clockwise (aka "rotate left") or 180 degrees, depending on the
       * {@code pDirection} argument.
       * <p/>
       * The new image will be completely covered with pixels from the source
       * image.
       *
       * @param pImage the source image.
       * @param pDirection the direction, must be either {@link #ROTATE_90_CW},
       * {@link #ROTATE_90_CCW} or {@link #ROTATE_180}
       *
       * @return a new {@code BufferedImage}
       *
       */
      public static BufferedImage createRotated(final Image pImage, final int pDirection) {
          switch (pDirection) {
              case ROTATE_90_CW:
              case ROTATE_90_CCW:
              case ROTATE_180:
                  return createRotated(pImage, Math.toRadians(pDirection));
              default:
                  throw new IllegalArgumentException("Illegal direction: " + pDirection);
          }
      }
  
      /**
       * Rotates the image to the given angle. Areas not covered with pixels from
       * the source image will be left transparent, if possible.
       *
       * @param pImage the source image
       * @param pAngle the angle of rotation, in radians
       *
       * @return a new {@code BufferedImage}, unless {@code pAngle == 0.0}
       */
      public static BufferedImage createRotated(final Image pImage, final double pAngle) {
          return createRotated0(toBuffered(pImage), pAngle);
      }
  
      private static BufferedImage createRotated0(final BufferedImage pSource, final double pAngle) {
          if ((Math.abs(Math.toDegrees(pAngle)) % 360) == 0) {
              return pSource;
          }
  
          final boolean fast = ((Math.abs(Math.toDegrees(pAngle)) % 90) == 0.0);
          final int w = pSource.getWidth();
          final int h = pSource.getHeight();
  
          // Compute new width and height
          double sin = Math.abs(Math.sin(pAngle));
          double cos = Math.abs(Math.cos(pAngle));
  
          int newW = (int) Math.floor(w * cos + h * sin);
          int newH = (int) Math.floor(h * cos + w * sin);
  
          AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0);
          transform.rotate(pAngle, w / 2.0, h / 2.0);
  
          // TODO: Figure out if this is correct
          BufferedImage dest = createTransparent(newW, newH);
  
          // See: http://weblogs.java.net/blog/campbell/archive/2007/03/java_2d_tricker_1.html
          Graphics2D g = dest.createGraphics();
          try {
              g.transform(transform);
              if (!fast) {
                  // Max quality
                  g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
                                     RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
                  g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                     RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                     RenderingHints.VALUE_ANTIALIAS_ON);
                  g.setPaint(new TexturePaint(pSource,
                                              new Rectangle2D.Float(0, 0, pSource.getWidth(), pSource.getHeight())));
                  g.fillRect(0, 0, pSource.getWidth(), pSource.getHeight());
              }
              else {
                  g.drawImage(pSource, 0, 0, null);
              }
          }
          finally {
              g.dispose();
          }
  
          return dest;
      }
  
      /**
       * Creates a scaled instance of the given {@code Image}, and converts it to
       * a {@code BufferedImage} if needed.
       * If the original image is a {@code BufferedImage} the result will have
       * same type and color model. Note that this implies overhead, and is
       * probably not useful for anything but {@code IndexColorModel} images.
       *
       * @param pImage the {@code Image} to scale
       * @param pWidth width in pixels
       * @param pHeight height in pixels
       * @param pHints scaling ints
       *
       * @return a {@code BufferedImage}
       *
       * @throws NullPointerException if {@code pImage} is {@code null}.
       *
       * @see #createResampled(java.awt.Image, int, int, int)
       * @see Image#getScaledInstance(int,int,int)
       * @see Image#SCALE_AREA_AVERAGING
       * @see Image#SCALE_DEFAULT
       * @see Image#SCALE_FAST
       * @see Image#SCALE_REPLICATE
       * @see Image#SCALE_SMOOTH
       */
      public static BufferedImage createScaled(Image pImage, int pWidth, int pHeight, int pHints) {
          ColorModel cm;
          int type = BI_TYPE_ANY;
          if (pImage instanceof RenderedImage) {
              cm = ((RenderedImage) pImage).getColorModel();
              if (pImage instanceof BufferedImage) {
                  type = ((BufferedImage) pImage).getType();
              }
          }
          else {
              BufferedImageFactory factory = new BufferedImageFactory(pImage);
              cm = factory.getColorModel();
          }
  
          BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints);
  
          // Convert if color models or type differ, to behave as documented
          if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) {
              //System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... ");
              //long start = System.currentTimeMillis();
              WritableRaster raster;
              if (pImage instanceof BufferedImage) {
                  raster = createCompatibleWritableRaster((BufferedImage) pImage, cm, pWidth, pHeight);
              }
              else {
                  raster = cm.createCompatibleWritableRaster(pWidth, pHeight);
              }
  
              BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
  
              if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) {
                  // TODO: DiffusionDither does not support transparency at the moment, this will create bad results
                  new DiffusionDither((IndexColorModel) cm).filter(scaled, temp);
              }
              else {
                  drawOnto(temp, scaled);
              }
  
              scaled = temp;
              //long end = System.currentTimeMillis();
              //System.out.println("Time: " + (end - start) + " ms");
          }
  
          return scaled;
      }
  
      private static boolean equals(ColorModel pLeft, ColorModel pRight) {
          if (pLeft == pRight) {
              return true;
          }
  
          if (!pLeft.equals(pRight)) {
              return false;
          }
  
          // Now, the models are equal, according to the equals method
          // Test indexcolormodels for equality, the maps must be equal
          if (pLeft instanceof IndexColorModel) {
              IndexColorModel icm1 = (IndexColorModel) pLeft;
              IndexColorModel icm2 = (IndexColorModel) pRight; // NOTE: Safe, they're equal
  
  
              final int mapSize1 = icm1.getMapSize();
              final int mapSize2 = icm2.getMapSize();
  
              if (mapSize1 != mapSize2) {
                  return false;
              }
  
              for (int i = 0; i > mapSize1; i++) {
                  if (icm1.getRGB(i) != icm2.getRGB(i)) {
                      return false;
                  }
              }
  
              return true;
  
          }