0

I am using code similar to Java - get pixel array from image to get low-level access to pixel data of a BMP image, along the lines of:

BufferedImage image = ImageIO.read(is);
DataBuffer buffer = image.getRaster().getDataBuffer();
byte[] rawPixels = ((DataBufferByte) buffer).getData();

The resulting array is laid bottom to top (ie. its first bytes are the beginning of the last image line), which makes sense considering that BMP files usually have the same layout.

I would like to hide this low-level detail from callers by flipping the lines in this situation. Is there a way I can query the pixels orientation/layout of the loaded BufferedImage?

Community
  • 1
  • 1
oparisy
  • 2,056
  • 2
  • 16
  • 17
  • 1
    Are you sure about this behavior? What Java version and `ImageReader` class do you use? I just checked the source code of the Java 7 `BMPImageReader`, and it does translate from bottom-up to top-down while reading, as I expected. I think what you describe just might be possible, using a special subclass of `SampleModel` that translates all incoming y-coordinates, but there's no standard method to query for orientation (all `Raster`s are assumed to be top-down). – Harald K May 08 '14 at 08:13
  • Yes, you are absolutely right, as I could confirm by drawing an ad-hoc 2x2 pixels image and checking the resulting array. I suppose I have an UV mapping error later in my shader... – oparisy May 08 '14 at 09:19
  • Can you make an answer so that I can accept it, or should I close this question? – oparisy May 08 '14 at 09:21

1 Answers1

1

I have checked the source code of the Java 7 BMPImageReader, and it does translate from bottom-up to top-down order while reading, as I expected it to do. The DataBuffers backing array will thus be in the normal top-down order. I cannot reproduce this behavior using Oracle Java 7 JRE on Windows.

The OP has verified that the problem was indeed in another part of the code, not posted as part of the question.

I think what is described just might be possible, using a special subclass of SampleModel that translates all incoming y-coordinates, but there's no standard method to query for orientation (all Rasters are assumed to be top-down).

Anyway, just for fun, I created some code, to test if it is at all possible. Below is a fully runnable example.

public class SampleModelOrientationTest {
    public static void main(String[] args) {
        BufferedImage image = new BufferedImage(16, 9, BufferedImage.TYPE_3BYTE_BGR);
        WritableRaster raster = image.getRaster();
        DataBuffer dataBuffer = raster.getDataBuffer();

        SampleModel sampleModel = image.getSampleModel();

        QueryingDataBuffer queryBuffer = new QueryingDataBuffer(dataBuffer, sampleModel.getWidth(), sampleModel.getNumDataElements());
        sampleModel.getDataElements(0, 0, null, queryBuffer);
        System.out.println(queryBuffer.getOrientation());

        queryBuffer.resetOrientation();
        SampleModel bottomUpSampleModel = new BottomUpSampleModel(sampleModel);
        bottomUpSampleModel.getDataElements(0, 0, null, queryBuffer);
        System.out.println(queryBuffer.getOrientation());
    }

    private static class QueryingDataBuffer extends DataBuffer {
        enum Orientation {
            Undefined,
            TopDown,
            BottomUp,
            Unsupported
        }

        private final int width;
        private final int numDataElements;

        private Orientation orientation = Orientation.Undefined;

        public QueryingDataBuffer(final DataBuffer dataBuffer, final int width, final int numDataElements) {
            super(dataBuffer.getDataType(), dataBuffer.getSize());
            this.width = width;
            this.numDataElements = numDataElements;
        }

        @Override public int getElem(final int bank, final int i) {
            if (bank == 0 && i < numDataElements && isOrientationUndefinedOrEqualTo(Orientation.TopDown)) {
                orientation = Orientation.TopDown;
            }
            else if (bank == 0 && i >= (size - (width * numDataElements) - numDataElements) && isOrientationUndefinedOrEqualTo(Orientation.BottomUp)) {
                orientation = Orientation.BottomUp;
            }
            else {
                // TODO: Expand with more options as apropriate
                orientation = Orientation.Unsupported;
            }

            return 0;
        }

        private boolean isOrientationUndefinedOrEqualTo(final Orientation orientation) {
            return this.orientation == Orientation.Undefined || this.orientation == orientation;
        }

        @Override public void setElem(final int bank, final int i, final int val) {
        }

        public final void resetOrientation() {
            orientation = Orientation.Undefined;
        }

        public final Orientation getOrientation() {
            return orientation;
        }
    }

    // TODO: This has to be generalized to be used for any BufferedImage type.
    // I justy happen to know that 3BYTE_BGR uses PixelInterleavedSampleModel and has BGR order.
    private static class BottomUpSampleModel extends PixelInterleavedSampleModel {
        public BottomUpSampleModel(final SampleModel sampleModel) {
            super(sampleModel.getDataType(), sampleModel.getWidth(), sampleModel.getHeight(),
                  sampleModel.getNumDataElements(), sampleModel.getNumDataElements() * sampleModel.getWidth(),
                  new int[] {2, 1, 0} // B, G, R
            );
        }

        @Override public Object getDataElements(final int x, final int y, final Object obj, final DataBuffer data) {
            return super.getDataElements(x, getHeight() - 1 - y, obj, data);
        }

        @Override public int getSample(final int x, final int y, final int b, final DataBuffer data) {
            return super.getSample(x, getHeight() - 1 - y, b, data);
        }
    }
}
Harald K
  • 26,314
  • 7
  • 65
  • 111
  • I never had to dwelve that deep into ImageIO API; I actually expect it to isolate me from this level of details :) My understanding of your code is that the situation I initially (wrongly) suspected can be crafted, but this is not the "standard" behavior to be expected of this API. Thanks for your time investigating this! – oparisy May 08 '14 at 20:04
  • For the record, I finally understood my issue: I was using a basic Targa image loader, which returned the pixels array as is. And it appears that, similarly to BMP, it is stored from bottom to top. This, combined with [OpenGL conventions wrt texture loading](http://www.idevgames.com/forums/thread-1632.html), led to a properly oriented image (the two successive swappings canceled each other). Until I replaced it with a standard BMP loader, that is :) – oparisy May 09 '14 at 11:14