Skip to content

Commit 84fccdc

Browse files
mrjoshuakclaude
andcommitted
Fix scanline decoder crash on non-zero origin data windows (fixes #2)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8061277 commit 84fccdc

1 file changed

Lines changed: 25 additions & 12 deletions

File tree

exr/scanline.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,6 @@ func (r *ScanlineReader) decodeUncompressedChunkParallel(chunkY int, data []byte
464464

465465
minY := int(r.dataWindow.Min.Y)
466466
maxY := int(r.dataWindow.Max.Y)
467-
minX := int(r.dataWindow.Min.X)
468467

469468
comp := r.header.Compression()
470469
linesPerChunk := comp.ScanlinesPerChunk()
@@ -483,6 +482,11 @@ func (r *ScanlineReader) decodeUncompressedChunkParallel(chunkY int, data []byte
483482
continue
484483
}
485484

485+
// Map from absolute data window coordinates to buffer-relative coordinates.
486+
// The framebuffer is allocated for the data window dimensions, so pixel
487+
// (minX, minY) maps to buffer position (0, 0).
488+
bufY := y - minY
489+
486490
for i := range channelInfos {
487491
ci := &channelInfos[i]
488492
if ci.slice == nil {
@@ -496,11 +500,11 @@ func (r *ScanlineReader) decodeUncompressedChunkParallel(chunkY int, data []byte
496500

497501
switch ci.ch.Type {
498502
case PixelTypeHalf:
499-
ci.slice.WriteRowHalfBytes(y, data[pos:pos+ci.bytesInChannel], minX, ci.pixelsInChannel)
503+
ci.slice.WriteRowHalfBytes(bufY, data[pos:pos+ci.bytesInChannel], 0, ci.pixelsInChannel)
500504
case PixelTypeFloat:
501-
ci.slice.WriteRowFloat(y, data[pos:pos+ci.bytesInChannel], minX, ci.pixelsInChannel)
505+
ci.slice.WriteRowFloat(bufY, data[pos:pos+ci.bytesInChannel], 0, ci.pixelsInChannel)
502506
case PixelTypeUint:
503-
ci.slice.WriteRowUint(y, data[pos:pos+ci.bytesInChannel], minX, ci.pixelsInChannel)
507+
ci.slice.WriteRowUint(bufY, data[pos:pos+ci.bytesInChannel], 0, ci.pixelsInChannel)
504508
}
505509
pos += ci.bytesInChannel
506510
}
@@ -519,7 +523,6 @@ func (r *ScanlineReader) decodeUncompressedChunk(chunkY int, data []byte) error
519523

520524
minY := int(r.dataWindow.Min.Y)
521525
maxY := int(r.dataWindow.Max.Y)
522-
minX := int(r.dataWindow.Min.X)
523526

524527
// Calculate how many lines are in this chunk
525528
compression := r.header.Compression()
@@ -540,6 +543,11 @@ func (r *ScanlineReader) decodeUncompressedChunk(chunkY int, data []byte) error
540543
continue
541544
}
542545

546+
// Map from absolute data window coordinates to buffer-relative coordinates.
547+
// The framebuffer is allocated for the data window dimensions, so pixel
548+
// (minX, minY) maps to buffer position (0, 0).
549+
bufY := y - minY
550+
543551
for i := range channelInfos {
544552
ci := &channelInfos[i]
545553
if ci.slice == nil {
@@ -553,11 +561,11 @@ func (r *ScanlineReader) decodeUncompressedChunk(chunkY int, data []byte) error
553561

554562
switch ci.ch.Type {
555563
case PixelTypeHalf:
556-
ci.slice.WriteRowHalfBytes(y, data[pos:pos+ci.bytesInChannel], minX, ci.pixelsInChannel)
564+
ci.slice.WriteRowHalfBytes(bufY, data[pos:pos+ci.bytesInChannel], 0, ci.pixelsInChannel)
557565
case PixelTypeFloat:
558-
ci.slice.WriteRowFloat(y, data[pos:pos+ci.bytesInChannel], minX, ci.pixelsInChannel)
566+
ci.slice.WriteRowFloat(bufY, data[pos:pos+ci.bytesInChannel], 0, ci.pixelsInChannel)
559567
case PixelTypeUint:
560-
ci.slice.WriteRowUint(y, data[pos:pos+ci.bytesInChannel], minX, ci.pixelsInChannel)
568+
ci.slice.WriteRowUint(bufY, data[pos:pos+ci.bytesInChannel], 0, ci.pixelsInChannel)
561569
}
562570
pos += ci.bytesInChannel
563571
}
@@ -981,7 +989,7 @@ func (w *ScanlineWriter) writePixelsParallel(y1, y2, minY, maxY int, comp Compre
981989
// encodeUncompressedChunk encodes scanlines as uncompressed data.
982990
func (w *ScanlineWriter) encodeUncompressedChunk(y1, y2 int) ([]byte, error) {
983991
width := int(w.dataWindow.Width())
984-
minX := int(w.dataWindow.Min.X)
992+
minY := int(w.dataWindow.Min.Y)
985993

986994
// Calculate buffer size
987995
bufSize := 0
@@ -1003,6 +1011,11 @@ func (w *ScanlineWriter) encodeUncompressedChunk(y1, y2 int) ([]byte, error) {
10031011
pos := 0 // Current position in output
10041012

10051013
for y := y1; y <= y2; y++ {
1014+
// Map from absolute data window coordinates to buffer-relative coordinates.
1015+
// The framebuffer is allocated for the data window dimensions, so pixel
1016+
// (minX, minY) maps to buffer position (0, 0).
1017+
bufY := y - minY
1018+
10061019
for _, ch := range sortedChannels {
10071020
pixelsInChannel := (width + int(ch.XSampling) - 1) / int(ch.XSampling)
10081021
bytesInChannel := pixelsInChannel * ch.Type.Size()
@@ -1020,7 +1033,7 @@ func (w *ScanlineWriter) encodeUncompressedChunk(y1, y2 int) ([]byte, error) {
10201033
// Use optimized bulk row operations
10211034
switch ch.Type {
10221035
case PixelTypeHalf:
1023-
slice.ReadRowHalf(y, halfBuf, minX, pixelsInChannel)
1036+
slice.ReadRowHalf(bufY, halfBuf, 0, pixelsInChannel)
10241037
// Convert uint16 to bytes
10251038
for i := 0; i < pixelsInChannel; i++ {
10261039
output[pos] = byte(halfBuf[i])
@@ -1029,11 +1042,11 @@ func (w *ScanlineWriter) encodeUncompressedChunk(y1, y2 int) ([]byte, error) {
10291042
}
10301043

10311044
case PixelTypeFloat:
1032-
slice.ReadRowFloat(y, output[pos:pos+bytesInChannel], minX, pixelsInChannel)
1045+
slice.ReadRowFloat(bufY, output[pos:pos+bytesInChannel], 0, pixelsInChannel)
10331046
pos += bytesInChannel
10341047

10351048
case PixelTypeUint:
1036-
slice.ReadRowUint(y, output[pos:pos+bytesInChannel], minX, pixelsInChannel)
1049+
slice.ReadRowUint(bufY, output[pos:pos+bytesInChannel], 0, pixelsInChannel)
10371050
pos += bytesInChannel
10381051
}
10391052
}

0 commit comments

Comments
 (0)