Switch rendering canvas to TYPE_INT_ARGB for better performance.
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Thu, 12 Mar 2026 18:53:14 +0000 (20:53 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Thu, 12 Mar 2026 18:53:14 +0000 (20:53 +0200)
src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/TextureBitmap.java

index d072874..336b61a 100644 (file)
@@ -10,7 +10,7 @@ import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
 
 import java.awt.*;
 import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
 import java.awt.image.WritableRaster;
 
 /**
@@ -37,9 +37,10 @@ import java.awt.image.WritableRaster;
 public class RenderingContext {
 
     /**
-     * The {@link BufferedImage} pixel format used for the rendering buffer (4-byte ABGR).
+     * The {@link BufferedImage} pixel format used for the rendering buffer.
+     * TYPE_INT_RGB provides optimal performance for Java2D blitting.
      */
-    public static final int bufferedImageType = BufferedImage.TYPE_4BYTE_ABGR;
+    public static final int bufferedImageType = BufferedImage.TYPE_INT_RGB;
 
     /**
      * Java2D graphics context for drawing text, anti-aliased shapes, and other
@@ -49,9 +50,9 @@ public class RenderingContext {
 
     /**
      * Pixels of the rendering area.
-     * Each pixel is represented by 4 bytes: alpha, blue, green, red.
+     * Each pixel is a single int in RGB format: {@code (r << 16) | (g << 8) | b}.
      */
-    public final byte[] pixels;
+    public final int[] pixels;
 
     /**
      * Width of the rendering area in pixels.
@@ -117,7 +118,7 @@ public class RenderingContext {
         bufferedImage = new BufferedImage(width, height, bufferedImageType);
 
         final WritableRaster raster = bufferedImage.getRaster();
-        final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
+        final DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer();
         pixels = dbi.getData();
 
         graphics = (Graphics2D) bufferedImage.getGraphics();
index 8ceefc2..ca3d296 100644 (file)
@@ -103,7 +103,7 @@ public class Billboard extends AbstractCoordinateShape {
         if (onScreenCappedXEnd > targetRenderingArea.width)
             onScreenCappedXEnd = targetRenderingArea.width;
 
-        final byte[] targetRenderingAreaBytes = targetRenderingArea.pixels;
+        final int[] targetRenderingAreaPixels = targetRenderingArea.pixels;
 
         final int textureWidth = textureBitmap.width;
 
@@ -112,15 +112,15 @@ public class Billboard extends AbstractCoordinateShape {
             final int sourceBitmapScanlinePixel = ((textureBitmap.height * (y - onScreenUncappedYStart)) / onScreenUncappedHeight)
                     * textureWidth;
 
-            int targetRenderingAreaOffset = ((y * targetRenderingArea.width) + onScreenCappedXStart) * 4;
+            int targetRenderingAreaOffset = (y * targetRenderingArea.width) + onScreenCappedXStart;
 
             for (int x = onScreenCappedXStart; x < onScreenCappedXEnd; x++) {
 
-                final int sourceBitmapPixelAddress = (sourceBitmapScanlinePixel + ((textureWidth * (x - onScreenUncappedXStart)) / onScreenUncappedWidth)) * 4;
+                final int sourceBitmapPixelAddress = sourceBitmapScanlinePixel + ((textureWidth * (x - onScreenUncappedXStart)) / onScreenUncappedWidth);
 
-                textureBitmap.drawPixel(sourceBitmapPixelAddress, targetRenderingAreaBytes, targetRenderingAreaOffset);
+                textureBitmap.drawPixel(sourceBitmapPixelAddress, targetRenderingAreaPixels, targetRenderingAreaOffset);
 
-                targetRenderingAreaOffset += 4;
+                targetRenderingAreaOffset++;
             }
         }
     }
index f770b73..59cc454 100644 (file)
@@ -76,21 +76,14 @@ public class GlowingPoint extends Billboard {
 
         for (int x = 0; x < TEXTURE_RESOLUTION_PIXELS; x++)
             for (int y = 0; y < TEXTURE_RESOLUTION_PIXELS; y++) {
-                int address = texture.primaryBitmap.getAddress(x, y);
-
                 final int distanceFromCenter = (int) sqrt(pow(halfResolution - x, 2) + pow(halfResolution - y, 2));
 
                 int alpha = 255 - ((270 * distanceFromCenter) / halfResolution);
                 if (alpha < 0)
                     alpha = 0;
 
-                texture.primaryBitmap.bytes[address] = (byte) alpha;
-                address++;
-                texture.primaryBitmap.bytes[address] = (byte) color.b;
-                address++;
-                texture.primaryBitmap.bytes[address] = (byte) color.g;
-                address++;
-                texture.primaryBitmap.bytes[address] = (byte) color.r;
+                texture.primaryBitmap.pixels[texture.primaryBitmap.getAddress(x, y)] =
+                    (alpha << 24) | (color.r << 16) | (color.g << 8) | color.b;
             }
 
         return texture;
index 9a71bb0..079f0ab 100644 (file)
@@ -101,14 +101,14 @@ public class Line extends AbstractCoordinateShape {
 
         final int drawnWidth = x2 - x1;
 
-        int offset = ((y * renderBuffer.width) + x1) * 4;
-        final byte[] offSreenBufferBytes = renderBuffer.pixels;
+        int offset = (y * renderBuffer.width) + x1;
+        final int[] pixels = renderBuffer.pixels;
 
         final int lineAlpha = color.a;
 
-        final int colorB = color.b;
-        final int colorG = color.g;
         final int colorR = color.r;
+        final int colorG = color.g;
+        final int colorB = color.b;
 
         for (int i = 0; i < drawnWidth; i++) {
 
@@ -117,14 +117,16 @@ public class Line extends AbstractCoordinateShape {
             final int realLineAlpha = (int) (lineAlpha * alphaMultiplier);
             final int backgroundAlpha = 255 - realLineAlpha;
 
-            offSreenBufferBytes[offset] = (byte) 255;
-            offset++;
-            offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorB * realLineAlpha)) / 256);
-            offset++;
-            offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorG * realLineAlpha)) / 256);
-            offset++;
-            offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorR * realLineAlpha)) / 256);
-            offset++;
+            final int dest = pixels[offset];
+            final int destR = (dest >> 16) & 0xff;
+            final int destG = (dest >> 8) & 0xff;
+            final int destB = dest & 0xff;
+
+            final int newR = ((destR * backgroundAlpha) + (colorR * realLineAlpha)) / 256;
+            final int newG = ((destG * backgroundAlpha) + (colorG * realLineAlpha)) / 256;
+            final int newB = ((destB * backgroundAlpha) + (colorB * realLineAlpha)) / 256;
+
+            pixels[offset++] = (newR << 16) | (newG << 8) | newB;
 
             d1 += dinc;
         }
@@ -158,12 +160,12 @@ public class Line extends AbstractCoordinateShape {
         if (lineWidth == 0)
             return;
 
-        final byte[] offSreenBufferBytes = buffer.pixels;
+        final int[] pixels = buffer.pixels;
         final int backgroundAlpha = 255 - alpha;
 
-        final int blueWithAlpha = color.b * alpha;
-        final int greenWithAplha = color.g * alpha;
         final int redWithAlpha = color.r * alpha;
+        final int greenWithAplha = color.g * alpha;
+        final int blueWithAlpha = color.b * alpha;
 
         for (int relativeX = 0; relativeX <= lineWidth; relativeX++) {
             final int x = xStart + relativeX;
@@ -172,15 +174,18 @@ public class Line extends AbstractCoordinateShape {
 
                 final int y = yBase + ((relativeX * lineHeight) / lineWidth);
                 if ((y >= 0) && (y < buffer.height)) {
-                    int ramOffset = ((y * buffer.width) + x) * 4;
-
-                    offSreenBufferBytes[ramOffset] = (byte) 255;
-                    ramOffset++;
-                    offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256);
-                    ramOffset++;
-                    offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + greenWithAplha) / 256);
-                    ramOffset++;
-                    offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256);
+                    int offset = (y * buffer.width) + x;
+
+                    final int dest = pixels[offset];
+                    final int destR = (dest >> 16) & 0xff;
+                    final int destG = (dest >> 8) & 0xff;
+                    final int destB = dest & 0xff;
+
+                    final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256;
+                    final int newG = ((destG * backgroundAlpha) + greenWithAplha) / 256;
+                    final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256;
+
+                    pixels[offset] = (newR << 16) | (newG << 8) | newB;
                 }
             }
         }
@@ -214,12 +219,12 @@ public class Line extends AbstractCoordinateShape {
         if (lineHeight == 0)
             return;
 
-        final byte[] offScreenBufferBytes = buffer.pixels;
+        final int[] pixels = buffer.pixels;
         final int backgroundAlpha = 255 - alpha;
 
-        final int blueWithAlpha = color.b * alpha;
-        final int greenWithAlpha = color.g * alpha;
         final int redWithAlpha = color.r * alpha;
+        final int greenWithAlpha = color.g * alpha;
+        final int blueWithAlpha = color.b * alpha;
 
         for (int relativeY = 0; relativeY <= lineHeight; relativeY++) {
             final int y = yStart + relativeY;
@@ -228,15 +233,18 @@ public class Line extends AbstractCoordinateShape {
 
                 final int x = xBase + ((relativeY * lineWidth) / lineHeight);
                 if ((x >= 0) && (x < buffer.width)) {
-                    int ramOffset = ((y * buffer.width) + x) * 4;
-
-                    offScreenBufferBytes[ramOffset] = (byte) 255;
-                    ramOffset++;
-                    offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256);
-                    ramOffset++;
-                    offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + greenWithAlpha) / 256);
-                    ramOffset++;
-                    offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256);
+                    int offset = (y * buffer.width) + x;
+
+                    final int dest = pixels[offset];
+                    final int destR = (dest >> 16) & 0xff;
+                    final int destG = (dest >> 8) & 0xff;
+                    final int destB = dest & 0xff;
+
+                    final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256;
+                    final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256;
+                    final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256;
+
+                    pixels[offset] = (newR << 16) | (newG << 8) | newB;
                 }
             }
         }
index eb53029..7cde339 100644 (file)
@@ -71,43 +71,37 @@ public class SolidPolygon extends AbstractCoordinateShape {
 
         final int width = x2 - x1;
 
-        int offset = ((y * renderBuffer.width) + x1) * 4;
-        final byte[] offScreenBufferBytes = renderBuffer.pixels;
+        int offset = (y * renderBuffer.width) + x1;
+        final int[] pixels = renderBuffer.pixels;
 
         final int polygonAlpha = color.a;
-        final int b = color.b;
-        final int g = color.g;
         final int r = color.r;
+        final int g = color.g;
+        final int b = color.b;
 
-        if (polygonAlpha == 255)
-            for (int i = 0; i < width; i++) {
-                offScreenBufferBytes[offset] = (byte) 255;
-                offset++;
-                offScreenBufferBytes[offset] = (byte) b;
-                offset++;
-                offScreenBufferBytes[offset] = (byte) g;
-                offset++;
-                offScreenBufferBytes[offset] = (byte) r;
-                offset++;
-            }
-        else {
+        if (polygonAlpha == 255) {
+            final int pixel = (r << 16) | (g << 8) | b;
+            for (int i = 0; i < width; i++)
+                pixels[offset++] = pixel;
+        } else {
             final int backgroundAlpha = 255 - polygonAlpha;
 
-            final int blueWithAlpha = b * polygonAlpha;
-            final int greenWithAlpha = g * polygonAlpha;
             final int redWithAlpha = r * polygonAlpha;
+            final int greenWithAlpha = g * polygonAlpha;
+            final int blueWithAlpha = b * polygonAlpha;
 
             for (int i = 0; i < width; i++) {
-                offScreenBufferBytes[offset] = (byte) 255;
-                offset++;
-                offScreenBufferBytes[offset] = (byte) ((((offScreenBufferBytes[offset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256);
-                offset++;
-                offScreenBufferBytes[offset] = (byte) ((((offScreenBufferBytes[offset] & 0xff) * backgroundAlpha) + greenWithAlpha) / 256);
-                offset++;
-                offScreenBufferBytes[offset] = (byte) ((((offScreenBufferBytes[offset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256);
-                offset++;
-            }
+                final int dest = pixels[offset];
+                final int destR = (dest >> 16) & 0xff;
+                final int destG = (dest >> 8) & 0xff;
+                final int destB = dest & 0xff;
+
+                final int newR = ((destR * backgroundAlpha) + redWithAlpha) / 256;
+                final int newG = ((destG * backgroundAlpha) + greenWithAlpha) / 256;
+                final int newB = ((destB * backgroundAlpha) + blueWithAlpha) / 256;
 
+                pixels[offset++] = (newR << 16) | (newG << 8) | newB;
+            }
         }
 
     }
index 45e9b98..c6c325a 100644 (file)
@@ -99,8 +99,8 @@ public class TexturedPolygon extends AbstractCoordinateShape {
         if (x2 >= renderBuffer.width)
             x2 = renderBuffer.width - 1;
 
-        int renderBufferOffset = ((y * renderBuffer.width) + x1) * 4;
-        final byte[] renderBufferBytes = renderBuffer.pixels;
+        int renderBufferOffset = (y * renderBuffer.width) + x1;
+        final int[] renderBufferPixels = renderBuffer.pixels;
 
         final double twidth = tx2 - tx1;
         final double theight = ty2 - ty1;
@@ -115,10 +115,10 @@ public class TexturedPolygon extends AbstractCoordinateShape {
             final int textureOffset = textureBitmap.getAddress((int) tx,
                     (int) ty);
 
-            textureBitmap.drawPixel(textureOffset, renderBufferBytes,
+            textureBitmap.drawPixel(textureOffset, renderBufferPixels,
                     renderBufferOffset);
 
-            renderBufferOffset += 4;
+            renderBufferOffset++;
         }
 
     }
index bf319b7..824c415 100644 (file)
@@ -4,11 +4,9 @@
  */
 package eu.svjatoslav.sixth.e3d.renderer.raster.texture;
 
-import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
-
 import java.awt.*;
 import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
 import java.awt.image.WritableRaster;
 
 import static java.util.Arrays.fill;
@@ -95,10 +93,10 @@ public class Texture {
         upSampled = new TextureBitmap[maxUpscale];
 
         final BufferedImage bufferedImage = new BufferedImage(width, height,
-                RenderingContext.bufferedImageType);
+                BufferedImage.TYPE_INT_ARGB);
 
         final WritableRaster raster = bufferedImage.getRaster();
-        final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer();
+        final DataBufferInt dbi = (DataBufferInt) raster.getDataBuffer();
         graphics = (Graphics2D) bufferedImage.getGraphics();
 
         graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
@@ -314,10 +312,8 @@ public class Texture {
      * A helper class that accumulates color values for a given area of a bitmap
      */
     public static class ColorAccumulator {
-        // Accumulated color values
         public int r, g, b, a;
 
-        // Number of pixels that have been accumulated
         public int pixelCount = 0;
 
         /**
@@ -329,19 +325,11 @@ public class Texture {
          */
         public void accumulate(final TextureBitmap bitmap, final int x,
                                final int y) {
-            int address = bitmap.getAddress(x, y);
-
-            a += bitmap.bytes[address] & 0xff;
-            address++;
-
-            b += bitmap.bytes[address] & 0xff;
-            address++;
-
-            g += bitmap.bytes[address] & 0xff;
-            address++;
-
-            r += bitmap.bytes[address] & 0xff;
-
+            final int pixel = bitmap.pixels[bitmap.getAddress(x, y)];
+            a += (pixel >> 24) & 0xff;
+            r += (pixel >> 16) & 0xff;
+            g += (pixel >> 8) & 0xff;
+            b += pixel & 0xff;
             pixelCount++;
         }
 
@@ -365,18 +353,11 @@ public class Texture {
          */
         public void storeResult(final TextureBitmap bitmap, final int x,
                                 final int y) {
-            int address = bitmap.getAddress(x, y);
-
-            bitmap.bytes[address] = (byte) (a / pixelCount);
-            address++;
-
-            bitmap.bytes[address] = (byte) (b / pixelCount);
-            address++;
-
-            bitmap.bytes[address] = (byte) (g / pixelCount);
-            address++;
-
-            bitmap.bytes[address] = (byte) (r / pixelCount);
+            final int avgA = a / pixelCount;
+            final int avgR = r / pixelCount;
+            final int avgG = g / pixelCount;
+            final int avgB = b / pixelCount;
+            bitmap.pixels[bitmap.getAddress(x, y)] = (avgA << 24) | (avgR << 16) | (avgG << 8) | avgB;
         }
     }
 
index a2d253d..7adb04f 100644 (file)
@@ -7,12 +7,11 @@ package eu.svjatoslav.sixth.e3d.renderer.raster.texture;
 import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
 
 /**
- * Represents a single resolution level of a texture as a raw byte array.
+ * Represents a single resolution level of a texture as a raw int array.
  *
- * <p>Each pixel is stored as 4 consecutive bytes in ABGR order:
- * Alpha, Blue, Green, Red. This byte ordering matches the
- * {@link java.awt.image.BufferedImage#TYPE_4BYTE_ABGR} format used by
- * the rendering pipeline.</p>
+ * <p>Each pixel is stored as a single int in ARGB format:
+ * {@code (alpha << 24) | (red << 16) | (green << 8) | blue}.
+ * This matches the {@link java.awt.image.BufferedImage#TYPE_INT_ARGB} format.</p>
  *
  * <p>{@code TextureBitmap} is used internally by {@link Texture} to represent
  * individual mipmap levels. The {@link #multiplicationFactor} records the
@@ -22,7 +21,7 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
  *
  * <p>This class provides low-level pixel operations including:</p>
  * <ul>
- *   <li>Alpha-blended pixel transfer to a target raster ({@link #drawPixel(int, byte[], int)})</li>
+ *   <li>Alpha-blended pixel transfer to a target raster ({@link #drawPixel(int, int[], int)})</li>
  *   <li>Direct pixel writes using engine {@link Color} ({@link #drawPixel(int, int, Color)})</li>
  *   <li>Filled rectangle drawing ({@link #drawRectangle(int, int, int, int, Color)})</li>
  *   <li>Full-surface color fill ({@link #fillColor(Color)})</li>
@@ -34,10 +33,11 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
 public class TextureBitmap {
 
     /**
-     * Raw pixel data in ABGR byte order (Alpha, Blue, Green, Red).
-     * The array length is {@code width * height * 4}.
+     * Raw pixel data in ARGB int format.
+     * Each int encodes: {@code (alpha << 24) | (red << 16) | (green << 8) | blue}.
+     * The array length is {@code width * height}.
      */
-    public final byte[] bytes;
+    public final int[] pixels;
 
     /**
      * The width of this bitmap in pixels.
@@ -55,8 +55,8 @@ public class TextureBitmap {
      */
     public double multiplicationFactor;
 
-    /**
-     * Creates a texture bitmap backed by an existing byte array.
+/**
+     * Creates a texture bitmap backed by an existing int array.
      *
      * <p>This constructor is typically used when the bitmap data is obtained from
      * a {@link java.awt.image.BufferedImage}'s raster, allowing direct access to
@@ -64,20 +64,20 @@ public class TextureBitmap {
      *
      * @param width                the bitmap width in pixels
      * @param height               the bitmap height in pixels
-     * @param bytes                the raw pixel data array (must be at least {@code width * height * 4} bytes)
+     * @param pixels               the raw pixel data array (must be at least {@code width * height} ints)
      * @param multiplicationFactor the scale factor relative to the native texture resolution
      */
-    public TextureBitmap(final int width, final int height, final byte[] bytes,
-                         final double multiplicationFactor) {
+    public TextureBitmap(final int width, final int height, final int[] pixels,
+                          final double multiplicationFactor) {
 
         this.width = width;
         this.height = height;
-        this.bytes = bytes;
+        this.pixels = pixels;
         this.multiplicationFactor = multiplicationFactor;
     }
 
     /**
-     * Creates a texture bitmap with a newly allocated byte array.
+     * Creates a texture bitmap with a newly allocated int array.
      *
      * <p>The pixel data array is initialized to all zeros (fully transparent black).</p>
      *
@@ -86,59 +86,51 @@ public class TextureBitmap {
      * @param multiplicationFactor the scale factor relative to the native texture resolution
      */
     public TextureBitmap(final int width, final int height,
-                         final double multiplicationFactor) {
+                          final double multiplicationFactor) {
 
-        this(width, height, new byte[width * height * 4], multiplicationFactor);
+        this(width, height, new int[width * height], multiplicationFactor);
     }
 
     /**
-     * Transfer (render) one pixel from current {@link TextureBitmap} to target raster bitmap.
+     * Transfer (render) one pixel from current {@link TextureBitmap} to target RGB raster.
      *
-     * @param sourceBitmapPixelAddress Pixel address within current {@link TextureBitmap} as indicated by its offset.
-     * @param targetBitmap             Bitmap of the target image where pixel should be rendered to.
-     * @param targetBitmapPixelAddress Pixel location within target image where pixel should be rendered to.
+     * <p>This texture stores pixels in ARGB format. The target is RGB format (no alpha).
+     * Alpha blending is performed based on the source pixel's alpha value.</p>
+     *
+     * @param sourcePixelAddress Pixel index within current texture.
+     * @param targetBitmap       Target RGB pixel array.
+     * @param targetPixelAddress Pixel index within target image.
      */
-    public void drawPixel(int sourceBitmapPixelAddress,
-                          final byte[] targetBitmap, int targetBitmapPixelAddress) {
+    public void drawPixel(final int sourcePixelAddress,
+                          final int[] targetBitmap, final int targetPixelAddress) {
 
-        final int textureAlpha = bytes[sourceBitmapPixelAddress] & 0xff;
+        final int sourcePixel = pixels[sourcePixelAddress];
+        final int textureAlpha = (sourcePixel >> 24) & 0xff;
 
         if (textureAlpha == 0)
             return;
 
         if (textureAlpha == 255) {
-            // skip reading of background for fully opaque pixels
-            targetBitmap[targetBitmapPixelAddress] = (byte) 255;
-
-            targetBitmapPixelAddress++;
-            sourceBitmapPixelAddress++;
-            targetBitmap[targetBitmapPixelAddress] = bytes[sourceBitmapPixelAddress];
-
-            targetBitmapPixelAddress++;
-            sourceBitmapPixelAddress++;
-            targetBitmap[targetBitmapPixelAddress] = bytes[sourceBitmapPixelAddress];
-
-            targetBitmapPixelAddress++;
-            sourceBitmapPixelAddress++;
-            targetBitmap[targetBitmapPixelAddress] = bytes[sourceBitmapPixelAddress];
+            targetBitmap[targetPixelAddress] = sourcePixel;
             return;
         }
 
         final int backgroundAlpha = 255 - textureAlpha;
-        sourceBitmapPixelAddress++;
 
-        targetBitmap[targetBitmapPixelAddress] = (byte) 255;
-        targetBitmapPixelAddress++;
+        final int srcR = (sourcePixel >> 16) & 0xff;
+        final int srcG = (sourcePixel >> 8) & 0xff;
+        final int srcB = sourcePixel & 0xff;
 
-        targetBitmap[targetBitmapPixelAddress] = (byte) ((((targetBitmap[targetBitmapPixelAddress] & 0xff) * backgroundAlpha) + ((bytes[sourceBitmapPixelAddress] & 0xff) * textureAlpha)) / 256);
-        sourceBitmapPixelAddress++;
-        targetBitmapPixelAddress++;
+        final int destPixel = targetBitmap[targetPixelAddress];
+        final int destR = (destPixel >> 16) & 0xff;
+        final int destG = (destPixel >> 8) & 0xff;
+        final int destB = destPixel & 0xff;
 
-        targetBitmap[targetBitmapPixelAddress] = (byte) ((((targetBitmap[targetBitmapPixelAddress] & 0xff) * backgroundAlpha) + ((bytes[sourceBitmapPixelAddress] & 0xff) * textureAlpha)) / 256);
-        sourceBitmapPixelAddress++;
-        targetBitmapPixelAddress++;
+        final int r = ((destR * backgroundAlpha) + (srcR * textureAlpha)) / 256;
+        final int g = ((destG * backgroundAlpha) + (srcG * textureAlpha)) / 256;
+        final int b = ((destB * backgroundAlpha) + (srcB * textureAlpha)) / 256;
 
-        targetBitmap[targetBitmapPixelAddress] = (byte) ((((targetBitmap[targetBitmapPixelAddress] & 0xff) * backgroundAlpha) + ((bytes[sourceBitmapPixelAddress] & 0xff) * textureAlpha)) / 256);
+        targetBitmap[targetPixelAddress] = (r << 16) | (g << 8) | b;
     }
 
     /**
@@ -152,18 +144,7 @@ public class TextureBitmap {
      * @param color the color to write
      */
     public void drawPixel(final int x, final int y, final Color color) {
-        int address = getAddress(x, y);
-
-        bytes[address] = (byte) color.a;
-
-        address++;
-        bytes[address] = (byte) color.b;
-
-        address++;
-        bytes[address] = (byte) color.g;
-
-        address++;
-        bytes[address] = (byte) color.r;
+        pixels[getAddress(x, y)] = (color.a << 24) | (color.r << 16) | (color.g << 8) | color.b;
     }
 
     /**
@@ -208,24 +189,13 @@ public class TextureBitmap {
      * @param color the color to fill the entire bitmap with
      */
     public void fillColor(final Color color) {
-        int address = 0;
-        while (address < bytes.length) {
-            bytes[address] = (byte) color.a;
-            address++;
-
-            bytes[address] = (byte) color.b;
-            address++;
-
-            bytes[address] = (byte) color.g;
-            address++;
-
-            bytes[address] = (byte) color.r;
-            address++;
-        }
+        final int pixel = (color.a << 24) | (color.r << 16) | (color.g << 8) | color.b;
+        for (int i = 0; i < pixels.length; i++)
+            pixels[i] = pixel;
     }
 
     /**
-     * Computes the byte offset into the {@link #bytes} array for the pixel at ({@code x}, {@code y}).
+     * Computes the index into the {@link #pixels} array for the pixel at ({@code x}, {@code y}).
      *
      * <p>Coordinates are clamped to the valid range {@code [0, width-1]} and
      * {@code [0, height-1]} so that out-of-bounds accesses are safely handled
@@ -233,7 +203,7 @@ public class TextureBitmap {
      *
      * @param x the x coordinate of the pixel
      * @param y the y coordinate of the pixel
-     * @return the byte offset of the first component (alpha) for the specified pixel
+     * @return the index into the pixels array for the specified pixel
      */
     public int getAddress(int x, int y) {
         if (x < 0)
@@ -248,6 +218,6 @@ public class TextureBitmap {
         if (y >= height)
             y = height - 1;
 
-        return ((y * width) + x) * 4;
+        return (y * width) + x;
     }
 }