@Override
    public ImageBuilder readImageData(final Rectangle subImageSpecification, final boolean hasAlpha, final boolean isAlphaPreMultiplied)
            throws IOException, ImagingException {

        final Rectangle subImage;
        if (subImageSpecification == null) {
            // configure subImage to read entire image
            subImage = new Rectangle(0, 0, width, height);
        } else {
            subImage = subImageSpecification;
        }

        final int bitsPerRow = tileWidth * bitsPerPixel;
        final int bytesPerRow = (bitsPerRow + 7) / 8;
        final int bytesPerTile = bytesPerRow * tileLength;

        // tileWidth is the width of the tile
        // tileLength is the height of the tile
        final int col0 = subImage.x / tileWidth;
        final int col1 = (subImage.x + subImage.width - 1) / tileWidth;
        final int row0 = subImage.y / tileLength;
        final int row1 = (subImage.y + subImage.height - 1) / tileLength;

        final int nCol = col1 - col0 + 1;
        final int nRow = row1 - row0 + 1;
        final int workingWidth = nCol * tileWidth;
        final int workingHeight = nRow * tileLength;

        final int nColumnsOfTiles = (width + tileWidth - 1) / tileWidth;

        final int x0 = col0 * tileWidth;
        final int y0 = row0 * tileLength;

        // When processing a subimage, the workingBuilder width and height
        // are set to be integral multiples of the tile width and height.
        // So the working image may be larger than the specified size of the subimage.
        // If necessary, the subimage is extracted from the workingBuilder
        // at the end of this method. This approach avoids the need for the
        // interpretTile method to implement bounds checking for a subimage.
        final ImageBuilder workingBuilder = new ImageBuilder(workingWidth, workingHeight, hasAlpha, isAlphaPreMultiplied);

        for (int iRow = row0; iRow <= row1; iRow++) {
            for (int iCol = col0; iCol <= col1; iCol++) {
                final int tile = iRow * nColumnsOfTiles + iCol;
                final byte[] compressed = imageData.tiles[tile].getData();
                final int x = iCol * tileWidth - x0;
                final int y = iRow * tileLength - y0;
                // Handle JPEG based compression
                if (compression == COMPRESSION_JPEG) {
                    if (planarConfiguration == TiffPlanarConfiguration.PLANAR) {
                        throw new ImagingException("TIFF file in non-supported configuration: JPEG compression used in planar configuration.");
                    }
                    DataInterpreterJpeg.intepretBlock(directory, workingBuilder, x, y, tileWidth, tileLength, compressed);
                    continue;
                }

                final byte[] decompressed = decompress(compressed, compression, bytesPerTile, tileWidth, tileLength);

                interpretTile(workingBuilder, decompressed, x, y, width, height);
            }
        }

        if (subImage.x == x0 && subImage.y == y0 && subImage.width == workingWidth && subImage.height == workingHeight) {
            return workingBuilder;
        }

        return workingBuilder.getSubset(subImage.x - x0, subImage.y - y0, subImage.width, subImage.height);
    }
Provides a data reader for TIFF file images organized by tiles.
src/main/java/org/apache/commons/imaging/formats/tiff/datareaders/DataReaderTiled.java