RFR: 8352407: PixelInterleavedSampleModel with unused components throws RasterFormatException: Incorrect pixel stride [v2]
Nikita Gubarkov
ngubarkov at openjdk.org
Wed Apr 2 07:50:31 UTC 2025
On Wed, 2 Apr 2025 06:26:13 GMT, Sergey Bylokhov <serb at openjdk.org> wrote:
>> Nikita Gubarkov has updated the pull request incrementally with one additional commit since the last revision:
>>
>> The previous approach was wrong for non-pixel interleaving.
>>
>> Just align the buffer size to the pixel stride instead, should be better.
>
> src/java.desktop/share/classes/java/awt/image/ComponentSampleModel.java line 285:
>
>> 283: // Align to the pixel stride.
>> 284: size = (size + pixelStride - 1) / pixelStride * pixelStride;
>> 285:
>
> Some thoughts I’d like to mention to ensure I understand the problem:
>
> This method should return the number of bytes required to store the image with given parameters—width, height, pixel stride, and scanline. It seems that the calculation could follow this approach:
>
> **starting offset + pointer to the last pixel + pixelStride**
>
> Is that a correct assumption?
>
> I have doubts that the logic in this method actually calculates the size properly, especially this line:
>
>
> int val = pixelStride * (width - 1);
> size += val;
>
> It seems like it should be:
>
> int val = pixelStride + pixelStride * (width - 1);
>
> or simply:
>
> int val = pixelStride * width;
>
> Or am I missing something?
As I understand, an important thing here is that this calculation needs to be correct for any kind of pixel/scanline/band interleaving.
What if our channels are stored separately (complete image of only R followed by complete image of G and so on) - your formula would only work if we change "**pointer to the last pixel**" -> "**pointer to the last band of the last pixel**". But then it would be wrong for the pixel-interleaving case (RGBA for each pixel stored together), because adding `pixelStride=4` to the "**last band of the last pixel**" would actually be 3 bytes more than needed.
The idea of the current approach, as I understand it is that we find the last band of the first pixel, be it 3 for pixel-interleaved RGBA, or 30000 for band-interleaved image, and then add 1 to it, meaning the size sufficient to fit that last band:
int size = maxBandOff + 1;
At this point we can already fit the first pixel, so what's left is to fit the remaining first scanline (hence `width - 1`):
int val = pixelStride * (width - 1);
size += val;
And the remaining rows (hence `height - 1`):
val = scanlineStride * (height - 1);
size += val;
### This gets us to our subject:
As current calculation only considers used bands by finding `maxBandOff`, it doesn't really care when we have pixel interleaving and `pixelStride > numBands`, it will happily cut any unused space at the end of the last pixel, thus causing various problems when we just want to, say, make an RGBx (`pixelStride=4`, `bandOffsets = {0, 1, 2}`) raster.
In my approach I just fixed that space which was cut by the previous calculation by aligning the size up to `pixelStride`. Not that I find this particularly elegant, but it seems to work and not cause regressions in non-pixel interleaved variants.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/24111#discussion_r2024265636
More information about the client-libs-dev
mailing list